diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cff64812..0248ba60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -on: [push] +on: [ push ] name: Continuous Integration # Inspired by osu! lazer's CI @@ -22,11 +22,6 @@ jobs: DB_USER: root DB_PASSWORD: lighthouse steps: - - name: Cancel previous runs of this workflow - uses: styfle/cancel-workflow-action@0.6.0 - with: - access_token: ${{ github.token }} - - name: Checkout uses: actions/checkout@v2 @@ -36,11 +31,11 @@ jobs: with: mysql-version: '8.0' root-password: ${{ env.DB_PASSWORD }} - + - name: Create Lighthouse Database if: ${{ matrix.os.database }} run: mysql -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} -h 127.0.0.1 -e "CREATE DATABASE ${{ env.DB_DATABASE }};"; - + - name: Install .NET 5.0 uses: actions/setup-dotnet@v1 with: @@ -52,10 +47,10 @@ jobs: with: dotnet-version: "6.0.x" include-prerelease: true - + - name: Compile run: dotnet build -c Debug - + - name: Test continue-on-error: true run: dotnet test --logger "trx;LogFileName=${{github.workspace}}/TestResults-${{matrix.os.prettyName}}.trx" @@ -84,7 +79,7 @@ jobs: create-status-check: false create-pr-comment: false update-comment-if-one-exists: false - + - name: Check Test Results if: steps.process-trx.outputs.test-outcome == 'Failed' run: | diff --git a/.run/Development Database.run.xml b/.run/Development Database.run.xml index 12a3e5fe..deac13d8 100644 --- a/.run/Development Database.run.xml +++ b/.run/Development Database.run.xml @@ -1,11 +1,12 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/ProjectLighthouse.Tests/DatabaseFact.cs b/ProjectLighthouse.Tests/DatabaseFact.cs index 93e2b07d..7fdc1524 100644 --- a/ProjectLighthouse.Tests/DatabaseFact.cs +++ b/ProjectLighthouse.Tests/DatabaseFact.cs @@ -2,12 +2,19 @@ using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.EntityFrameworkCore; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests { - public sealed class DatabaseFact : FactAttribute { - public DatabaseFact() { +namespace LBPUnion.ProjectLighthouse.Tests +{ + public sealed class DatabaseFact : FactAttribute + { + public DatabaseFact() + { ServerSettings.DbConnectionString = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse"; - if(!ServerSettings.DbConnected) this.Skip = "Database not available"; - else { + if (!ServerSettings.DbConnected) + { + this.Skip = "Database not available"; + } + else + { using Database database = new(); database.Database.Migrate(); } diff --git a/ProjectLighthouse.Tests/LighthouseTest.cs b/ProjectLighthouse.Tests/LighthouseTest.cs index d9027f7f..e3966fe1 100644 --- a/ProjectLighthouse.Tests/LighthouseTest.cs +++ b/ProjectLighthouse.Tests/LighthouseTest.cs @@ -8,31 +8,33 @@ using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; -namespace LBPUnion.ProjectLighthouse.Tests { +namespace LBPUnion.ProjectLighthouse.Tests +{ [SuppressMessage("ReSharper", "UnusedMember.Global")] - public class LighthouseTest { - public readonly TestServer Server; + public class LighthouseTest + { public readonly HttpClient Client; + public readonly TestServer Server; + + public LighthouseTest() + { + this.Server = new TestServer(new WebHostBuilder().UseStartup()); - public LighthouseTest() { - this.Server = new TestServer(new WebHostBuilder() - .UseStartup()); - this.Client = this.Server.CreateClient(); } - public async Task AuthenticateResponse(int number = 0) { - const char nullChar = (char)0x00; - const char sepChar = (char)0x20; + public async Task AuthenticateResponse(int number = 0) + { const string username = "unitTestUser"; - string stringContent = $"{nullChar}{sepChar}{username}{number}{nullChar}"; - + string stringContent = $"{LoginData.UsernamePrefix}{username}{number}{(char)0x00}"; + HttpResponseMessage response = await this.Client.PostAsync("/LITTLEBIGPLANETPS3_XML/login", new StringContent(stringContent)); return response; } - - public async Task Authenticate(int number = 0) { + + public async Task Authenticate(int number = 0) + { HttpResponseMessage response = await this.AuthenticateResponse(number); string responseContent = LbpSerializer.StringElement("loginResult", await response.Content.ReadAsStringAsync()); @@ -43,30 +45,31 @@ namespace LBPUnion.ProjectLighthouse.Tests { public Task AuthenticatedRequest(string endpoint, string mmAuth) => this.AuthenticatedRequest(endpoint, mmAuth, HttpMethod.Get); - public Task AuthenticatedRequest(string endpoint, string mmAuth, HttpMethod method) { - using var requestMessage = new HttpRequestMessage(method, endpoint); + public Task AuthenticatedRequest(string endpoint, string mmAuth, HttpMethod method) + { + using HttpRequestMessage? requestMessage = new(method, endpoint); requestMessage.Headers.Add("Cookie", mmAuth); return this.Client.SendAsync(requestMessage); } - public async Task UploadFileRequest(string endpoint, string filePath) { - return await this.Client.PostAsync(endpoint, new StringContent(await File.ReadAllTextAsync(filePath))); - } + public async Task UploadFileRequest(string endpoint, string filePath) + => await this.Client.PostAsync(endpoint, new StringContent(await File.ReadAllTextAsync(filePath))); - public async Task UploadDataRequest(string endpoint, byte[] data) { - return await this.Client.PostAsync(endpoint, new ByteArrayContent(data)); - } + public async Task UploadDataRequest(string endpoint, byte[] data) + => await this.Client.PostAsync(endpoint, new ByteArrayContent(data)); - public async Task AuthenticatedUploadFileRequest(string endpoint, string filePath, string mmAuth) { - using var requestMessage = new HttpRequestMessage(HttpMethod.Post, endpoint); + public async Task AuthenticatedUploadFileRequest(string endpoint, string filePath, string mmAuth) + { + 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 AuthenticatedUploadDataRequest(string endpoint, byte[] data, string mmAuth) { - using var requestMessage = new HttpRequestMessage(HttpMethod.Post, endpoint); + public async Task 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); diff --git a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj index 1b66add2..99d6e9bc 100644 --- a/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj +++ b/ProjectLighthouse.Tests/ProjectLighthouse.Tests.csproj @@ -13,9 +13,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -27,7 +27,7 @@ - + PreserveNewest diff --git a/ProjectLighthouse.Tests/Tests/AuthenticationTests.cs b/ProjectLighthouse.Tests/Tests/AuthenticationTests.cs index 5ac770ff..87bdf599 100644 --- a/ProjectLighthouse.Tests/Tests/AuthenticationTests.cs +++ b/ProjectLighthouse.Tests/Tests/AuthenticationTests.cs @@ -5,10 +5,13 @@ using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Settings; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests { - public class AuthenticationTests : LighthouseTest { +namespace LBPUnion.ProjectLighthouse.Tests +{ + public class AuthenticationTests : LighthouseTest + { [Fact] - public async Task ShouldReturnErrorOnNoPostData() { + public async Task ShouldReturnErrorOnNoPostData() + { HttpResponseMessage response = await this.Client.PostAsync("/LITTLEBIGPLANETPS3_XML/login", null!); Assert.False(response.IsSuccessStatusCode); #if NET6_0_OR_GREATER @@ -19,7 +22,8 @@ namespace LBPUnion.ProjectLighthouse.Tests { } [DatabaseFact] - public async Task ShouldReturnWithValidData() { + public async Task ShouldReturnWithValidData() + { HttpResponseMessage response = await this.AuthenticateResponse(); Assert.True(response.IsSuccessStatusCode); string responseContent = await response.Content.ReadAsStringAsync(); @@ -28,9 +32,10 @@ namespace LBPUnion.ProjectLighthouse.Tests { } [DatabaseFact] - public async Task CanSerializeBack() { + public async Task CanSerializeBack() + { LoginResult loginResult = await this.Authenticate(); - + Assert.NotNull(loginResult); Assert.NotNull(loginResult.AuthTicket); Assert.NotNull(loginResult.LbpEnvVer); @@ -40,18 +45,20 @@ namespace LBPUnion.ProjectLighthouse.Tests { } [DatabaseFact] - public async Task CanUseToken() { + public async Task CanUseToken() + { LoginResult loginResult = await this.Authenticate(); HttpResponseMessage response = await this.AuthenticatedRequest("/LITTLEBIGPLANETPS3_XML/eula", loginResult.AuthTicket); string responseContent = await response.Content.ReadAsStringAsync(); - + Assert.True(response.IsSuccessStatusCode); - Assert.Contains("You are logged in", responseContent); + Assert.Contains("You are now logged in", responseContent); } [DatabaseFact] - public async Task ShouldReturnForbiddenWhenNotAuthenticated() { + public async Task ShouldReturnForbiddenWhenNotAuthenticated() + { HttpResponseMessage response = await this.Client.GetAsync("/LITTLEBIGPLANETPS3_XML/eula"); Assert.False(response.IsSuccessStatusCode); Assert.True(response.StatusCode == HttpStatusCode.Forbidden); diff --git a/ProjectLighthouse.Tests/Tests/DatabaseTests.cs b/ProjectLighthouse.Tests/Tests/DatabaseTests.cs index 1e2cf60e..90551c5d 100644 --- a/ProjectLighthouse.Tests/Tests/DatabaseTests.cs +++ b/ProjectLighthouse.Tests/Tests/DatabaseTests.cs @@ -1,10 +1,13 @@ using System; using System.Threading.Tasks; -namespace LBPUnion.ProjectLighthouse.Tests { - public class DatabaseTests : LighthouseTest { +namespace LBPUnion.ProjectLighthouse.Tests +{ + public class DatabaseTests : LighthouseTest + { [DatabaseFact] - public async Task CanCreateUserTwice() { + public async Task CanCreateUserTwice() + { await using Database database = new(); int rand = new Random().Next(); diff --git a/ProjectLighthouse.Tests/Tests/FileTypeTests.cs b/ProjectLighthouse.Tests/Tests/FileTypeTests.cs index 61960ebe..b7229aa2 100644 --- a/ProjectLighthouse.Tests/Tests/FileTypeTests.cs +++ b/ProjectLighthouse.Tests/Tests/FileTypeTests.cs @@ -4,47 +4,56 @@ using System.Text; using LBPUnion.ProjectLighthouse.Types.Files; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests { - public class FileTypeTests { +namespace LBPUnion.ProjectLighthouse.Tests +{ + public class FileTypeTests + { [Fact] - public void ShouldRecognizeLevel() { + public void ShouldRecognizeLevel() + { LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestLevel.lvl")); Assert.True(file.FileType == LbpFileType.Level); } [Fact] - public void ShouldRecognizeScript() { + public void ShouldRecognizeScript() + { LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestScript.ff")); Assert.True(file.FileType == LbpFileType.Script); } [Fact] - public void ShouldRecognizeTexture() { + public void ShouldRecognizeTexture() + { LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestTexture.tex")); Assert.True(file.FileType == LbpFileType.Texture); } [Fact] - public void ShouldRecognizeFileArchive() { + public void ShouldRecognizeFileArchive() + { LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestFarc.farc")); Assert.True(file.FileType == LbpFileType.FileArchive); } [Fact] - public void ShouldNotRecognizeFileArchiveAsScript() { + public void ShouldNotRecognizeFileArchiveAsScript() + { LbpFile file = new(File.ReadAllBytes("ExampleFiles/TestFarc.farc")); Assert.False(file.FileType == LbpFileType.Script); Assert.True(file.FileType == LbpFileType.FileArchive); } [Fact] - public void ShouldRecognizeNothingAsUnknown() { + public void ShouldRecognizeNothingAsUnknown() + { LbpFile file = new(Array.Empty()); Assert.True(file.FileType == LbpFileType.Unknown); } [Fact] - public void ShouldRecognizeGarbageAsUnknown() { + public void ShouldRecognizeGarbageAsUnknown() + { LbpFile file = new(Encoding.ASCII.GetBytes("free pc only $900")); Assert.True(file.FileType == LbpFileType.Unknown); } diff --git a/ProjectLighthouse.Tests/Tests/MatchTests.cs b/ProjectLighthouse.Tests/Tests/MatchTests.cs index e6ba1207..5c52f9f5 100644 --- a/ProjectLighthouse.Tests/Tests/MatchTests.cs +++ b/ProjectLighthouse.Tests/Tests/MatchTests.cs @@ -1,39 +1,59 @@ using System; using System.Net.Http; +using System.Text; using System.Threading; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Types; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests { - public class MatchTests : LighthouseTest { +namespace LBPUnion.ProjectLighthouse.Tests +{ + public class MatchTests : LighthouseTest + { private static readonly SemaphoreSlim semaphore = new(1, 1); [DatabaseFact] - public async Task ShouldReturnOk() { + public async Task ShouldRejectEmptyData() + { LoginResult loginResult = await this.Authenticate(); await semaphore.WaitAsync(); HttpResponseMessage result = await this.AuthenticatedUploadDataRequest("LITTLEBIGPLANETPS3_XML/match", Array.Empty(), loginResult.AuthTicket); - Assert.True(result.IsSuccessStatusCode); semaphore.Release(); + Assert.False(result.IsSuccessStatusCode); + } + + [DatabaseFact] + public async Task ShouldReturnOk() + { + LoginResult loginResult = await this.Authenticate(); + await semaphore.WaitAsync(); + + HttpResponseMessage result = await this.AuthenticatedUploadDataRequest + ("LITTLEBIGPLANETPS3_XML/match", Encoding.ASCII.GetBytes("[UpdateMyPlayerData,[\"Player\":\"1984\"]]"), loginResult.AuthTicket); + + semaphore.Release(); + Assert.True(result.IsSuccessStatusCode); } public async Task GetPlayerCount() => Convert.ToInt32(await this.Client.GetStringAsync("LITTLEBIGPLANETPS3_XML/totalPlayerCount")); [DatabaseFact] - public async Task ShouldIncrementPlayerCount() { + public async Task ShouldIncrementPlayerCount() + { LoginResult loginResult = await this.Authenticate(new Random().Next()); await semaphore.WaitAsync(); int oldPlayerCount = await this.GetPlayerCount(); - HttpResponseMessage result = await this.AuthenticatedUploadDataRequest("LITTLEBIGPLANETPS3_XML/match", Array.Empty(), loginResult.AuthTicket); + HttpResponseMessage result = await this.AuthenticatedUploadDataRequest + ("LITTLEBIGPLANETPS3_XML/match", Encoding.ASCII.GetBytes("[UpdateMyPlayerData,[\"Player\":\"1984\"]]"), loginResult.AuthTicket); + Assert.True(result.IsSuccessStatusCode); int playerCount = await this.GetPlayerCount(); - + semaphore.Release(); Assert.Equal(oldPlayerCount + 1, playerCount); } diff --git a/ProjectLighthouse.Tests/Tests/SerializerTests.cs b/ProjectLighthouse.Tests/Tests/SerializerTests.cs index 14c1f5c8..2c8af05e 100644 --- a/ProjectLighthouse.Tests/Tests/SerializerTests.cs +++ b/ProjectLighthouse.Tests/Tests/SerializerTests.cs @@ -2,30 +2,42 @@ using System.Collections.Generic; using LBPUnion.ProjectLighthouse.Serialization; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests { - public class SerializerTests : LighthouseTest { +namespace LBPUnion.ProjectLighthouse.Tests +{ + public class SerializerTests : LighthouseTest + { [Fact] - public void BlankElementWorks() { + public void BlankElementWorks() + { Assert.Equal("", LbpSerializer.BlankElement("test")); } [Fact] - public void StringElementWorks() { + public void StringElementWorks() + { Assert.Equal("asd", LbpSerializer.StringElement("test", "asd")); Assert.Equal("asd", LbpSerializer.StringElement(new KeyValuePair("test", "asd"))); } [Fact] - public void TaggedStringElementWorks() { + public void TaggedStringElementWorks() + { Assert.Equal("asd", LbpSerializer.TaggedStringElement("test", "asd", "foo", "bar")); - Assert.Equal("asd", LbpSerializer.TaggedStringElement(new KeyValuePair("test", "asd"), - new KeyValuePair("foo", "bar"))); + Assert.Equal + ( + "asd", + LbpSerializer.TaggedStringElement(new KeyValuePair("test", "asd"), new KeyValuePair("foo", "bar")) + ); } [Fact] - public void ElementsWorks() { - Assert.Equal("asdbar", LbpSerializer.Elements(new KeyValuePair("test", "asd"), - new KeyValuePair("foo", "bar"))); + public void ElementsWorks() + { + Assert.Equal + ( + "asdbar", + LbpSerializer.Elements(new KeyValuePair("test", "asd"), new KeyValuePair("foo", "bar")) + ); } } } \ No newline at end of file diff --git a/ProjectLighthouse.Tests/Tests/SlotTests.cs b/ProjectLighthouse.Tests/Tests/SlotTests.cs index 3b136857..ae5a8b9b 100644 --- a/ProjectLighthouse.Tests/Tests/SlotTests.cs +++ b/ProjectLighthouse.Tests/Tests/SlotTests.cs @@ -4,10 +4,13 @@ using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Profiles; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests { - public class SlotTests : LighthouseTest { +namespace LBPUnion.ProjectLighthouse.Tests +{ + public class SlotTests : LighthouseTest + { [DatabaseFact] - public async Task ShouldOnlyShowUsersLevels() { + public async Task ShouldOnlyShowUsersLevels() + { await using Database database = new(); User userA = await database.CreateUser("unitTestUser0"); @@ -17,7 +20,8 @@ namespace LBPUnion.ProjectLighthouse.Tests { database.Locations.Add(l); await database.SaveChangesAsync(); - Slot slotA = new() { + Slot slotA = new() + { Creator = userA, Name = "slotA", Location = l, @@ -25,7 +29,8 @@ namespace LBPUnion.ProjectLighthouse.Tests { ResourceCollection = "", }; - Slot slotB = new() { + Slot slotB = new() + { Creator = userB, Name = "slotB", Location = l, @@ -47,7 +52,7 @@ namespace LBPUnion.ProjectLighthouse.Tests { Assert.NotEqual(respA, respB); Assert.DoesNotContain(respA, "slotB"); Assert.DoesNotContain(respB, "slotA"); - + // Cleanup database.Slots.Remove(slotA); diff --git a/ProjectLighthouse.Tests/Tests/UploadTests.cs b/ProjectLighthouse.Tests/Tests/UploadTests.cs index 30190fc3..41612256 100644 --- a/ProjectLighthouse.Tests/Tests/UploadTests.cs +++ b/ProjectLighthouse.Tests/Tests/UploadTests.cs @@ -4,39 +4,47 @@ using System.Net.Http; using System.Threading.Tasks; using Xunit; -namespace LBPUnion.ProjectLighthouse.Tests { - public class UploadTests : LighthouseTest { - public UploadTests() { +namespace LBPUnion.ProjectLighthouse.Tests +{ + public class UploadTests : LighthouseTest + { + public UploadTests() + { string assetsDirectory = Path.Combine(Environment.CurrentDirectory, "r"); - if(Directory.Exists(assetsDirectory)) Directory.Delete(assetsDirectory, true); + if (Directory.Exists(assetsDirectory)) Directory.Delete(assetsDirectory, true); } - + [Fact] - public async Task ShouldNotAcceptScript() { + public async Task ShouldNotAcceptScript() + { HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/scriptTest", "ExampleFiles/TestScript.ff"); Assert.False(response.IsSuccessStatusCode); } [Fact] - public async Task ShouldNotAcceptFarc() { + public async Task ShouldNotAcceptFarc() + { HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/farcTest", "ExampleFiles/TestFarc.farc"); Assert.False(response.IsSuccessStatusCode); } [Fact] - public async Task ShouldNotAcceptGarbage() { + public async Task ShouldNotAcceptGarbage() + { HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/garbageTest", "ExampleFiles/TestGarbage.bin"); Assert.False(response.IsSuccessStatusCode); } [Fact] - public async Task ShouldAcceptTexture() { + public async Task ShouldAcceptTexture() + { HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/textureTest", "ExampleFiles/TestTexture.tex"); Assert.True(response.IsSuccessStatusCode); } [Fact] - public async Task ShouldAcceptLevel() { + public async Task ShouldAcceptLevel() + { HttpResponseMessage response = await this.UploadFileRequest("/LITTLEBIGPLANETPS3_XML/upload/levelTest", "ExampleFiles/TestLevel.lvl"); Assert.True(response.IsSuccessStatusCode); } diff --git a/ProjectLighthouse.sln.DotSettings b/ProjectLighthouse.sln.DotSettings index 2ffd35fb..8b093e41 100644 --- a/ProjectLighthouse.sln.DotSettings +++ b/ProjectLighthouse.sln.DotSettings @@ -1,8 +1,81 @@  + HINT + HINT + SUGGESTION + WARNING + SUGGESTION + SUGGESTION + WARNING + ERROR + ExpressionBody + ExpressionBody + Field, Property, Event, Method + True + True + NEXT_LINE + NEXT_LINE + TOGETHER + True + USUAL_INDENT + USUAL_INDENT + NEXT_LINE + NEXT_LINE + 1 + 1 + False + False + False + False + False + False + False + 5 + 1 + EXPANDED + NEXT_LINE + NEVER + NEVER + NEVER + False + IF_OWNER_IS_SINGLE_LINE + False + False + True + True + True + True + True + True + True + NEXT_LINE + True + True + CHOP_IF_LONG + CHOP_IF_LONG + True + True + True + True + True + True + CHOP_IF_LONG + CHOP_IF_LONG + CHOP_IF_LONG + CHOP_ALWAYS + CHOP_IF_LONG + CHOP_ALWAYS + UseExplicitType + UseExplicitType + UseExplicitType MM <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="aaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + True + True + True + True + True True True True @@ -11,6 +84,7 @@ True True True + True True True True diff --git a/ProjectLighthouse/Controllers/ClientConfigurationController.cs b/ProjectLighthouse/Controllers/ClientConfigurationController.cs index dc3d90a7..fc12c2e7 100644 --- a/ProjectLighthouse/Controllers/ClientConfigurationController.cs +++ b/ProjectLighthouse/Controllers/ClientConfigurationController.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using LBPUnion.ProjectLighthouse.Types.Settings; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace LBPUnion.ProjectLighthouse.Controllers @@ -13,24 +14,20 @@ namespace LBPUnion.ProjectLighthouse.Controllers [SuppressMessage("ReSharper", "StringLiteralTypo")] public IActionResult NetworkSettings() { - var hostname = this.Request.Host; - return this.Ok( - "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 true\nAllowModeratedPoppetItems true\nShowLevelBoos true\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 120.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 30.0\nTIMEOUT_DIVE_IN_TOTAL 1000000.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\nCDNHostName localhost\nTelemetryServer localhost\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}\nCDNHostName {hostname}"); + HostString hostname = this.Request.Host; + return this.Ok + ( + "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 true\nAllowModeratedPoppetItems true\nShowLevelBoos true\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 120.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 30.0\nTIMEOUT_DIVE_IN_TOTAL 1000000.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\nCDNHostName localhost\nTelemetryServer localhost\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}\nCDNHostName {hostname}" + ); } [HttpGet("t_conf")] [Produces("text/json")] - public IActionResult Conf() - { - return this.Ok("[{\"StatusCode\":200}]"); - } + public IActionResult Conf() => this.Ok("[{\"StatusCode\":200}]"); [HttpGet("farc_hashes")] - public IActionResult FarcHashes() - { - return this.Ok(); - } + public IActionResult FarcHashes() => this.Ok(); [HttpGet("privacySettings")] [Produces("text/xml")] diff --git a/ProjectLighthouse/Controllers/CommentController.cs b/ProjectLighthouse/Controllers/CommentController.cs index 9f2967f1..5d673e5e 100644 --- a/ProjectLighthouse/Controllers/CommentController.cs +++ b/ProjectLighthouse/Controllers/CommentController.cs @@ -3,28 +3,34 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Profiles; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] [Produces("text/xml")] - public class CommentController : ControllerBase { + public class CommentController : ControllerBase + { private readonly Database database; - public CommentController(Database database) { + public CommentController(Database database) + { this.database = database; } [HttpGet("userComments/{username}")] - public async Task GetComments(string username) { - List comments = await this.database.Comments - .Include(c => c.Target) + public async Task GetComments(string username) + { + List comments = await this.database.Comments.Include + (c => c.Target) .Include(c => c.Poster) .Where(c => c.Target.Username == username) + .OrderByDescending(c => c.Timestamp) .ToListAsync(); string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + comment.Serialize()); @@ -32,7 +38,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers { } [HttpPost("postUserComment/{username}")] - public async Task PostComment(string username) { + public async Task PostComment(string username) + { this.Request.Body.Position = 0; string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); @@ -41,18 +48,36 @@ namespace LBPUnion.ProjectLighthouse.Controllers { User poster = await this.database.UserFromRequest(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); - - if(comment == null || target == null) return this.BadRequest(); + + if (comment == null || target == null) return this.BadRequest(); comment.PosterUserId = poster.UserId; comment.TargetUserId = target.UserId; + comment.Timestamp = TimeHelper.UnixTimeMilliseconds(); + this.database.Comments.Add(comment); await this.database.SaveChangesAsync(); return this.Ok(); } + + [HttpPost("deleteUserComment/{username}")] + public async Task DeleteComment([FromQuery] int commentId, string username) + { + User user = await this.database.UserFromRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); + + Comment comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); + + if (comment.TargetUserId != user.UserId && comment.PosterUserId != user.UserId) return this.StatusCode(403, ""); + + this.database.Comments.Remove(comment); + await this.database.SaveChangesAsync(); + + return this.Ok(); + } } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/DeveloperController.cs b/ProjectLighthouse/Controllers/DeveloperController.cs index 223ad66a..0ec94875 100644 --- a/ProjectLighthouse/Controllers/DeveloperController.cs +++ b/ProjectLighthouse/Controllers/DeveloperController.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; namespace LBPUnion.ProjectLighthouse.Controllers { @@ -8,9 +7,6 @@ namespace LBPUnion.ProjectLighthouse.Controllers public class DeveloperController : Controller { [HttpGet("/developer_videos")] - public IActionResult DeveloperVideos() - { - return this.Ok(); - } + public IActionResult DeveloperVideos() => this.Ok(); } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/EnterLevelController.cs b/ProjectLighthouse/Controllers/EnterLevelController.cs index 88d3933f..4b6c5236 100644 --- a/ProjectLighthouse/Controllers/EnterLevelController.cs +++ b/ProjectLighthouse/Controllers/EnterLevelController.cs @@ -1,13 +1,13 @@ using Microsoft.AspNetCore.Mvc; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/enterLevel")] // [Produces("text/plain")] - public class EnterLevelController : ControllerBase { + public class EnterLevelController : ControllerBase + { [HttpGet("enterLevel/{id}")] - public IActionResult EnterLevel(string id) { - return this.Ok(); - } + public IActionResult EnterLevel(string id) => this.Ok(); } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/LevelTagsController.cs b/ProjectLighthouse/Controllers/LevelTagsController.cs index 1ed96dc6..1795a868 100644 --- a/ProjectLighthouse/Controllers/LevelTagsController.cs +++ b/ProjectLighthouse/Controllers/LevelTagsController.cs @@ -2,17 +2,21 @@ using System; using LBPUnion.ProjectLighthouse.Types.Levels; using Microsoft.AspNetCore.Mvc; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/tags")] [Produces("text/plain")] - public class LevelTagsController : ControllerBase { + public class LevelTagsController : ControllerBase + { [HttpGet] - public IActionResult Get() { + public IActionResult Get() + { string[] tags = Enum.GetNames(typeof(LevelTags)); int i = 0; - foreach(string tag in tags) { + foreach (string tag in tags) + { tags[i] = $"TAG_{tag.Replace("_", "-")}"; i++; } diff --git a/ProjectLighthouse/Controllers/ListController.cs b/ProjectLighthouse/Controllers/ListController.cs index ede363be..0b1deefb 100644 --- a/ProjectLighthouse/Controllers/ListController.cs +++ b/ProjectLighthouse/Controllers/ListController.cs @@ -8,23 +8,28 @@ using LBPUnion.ProjectLighthouse.Types.Levels; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] [Produces("text/xml")] - public class ListController : ControllerBase { + public class ListController : ControllerBase + { private readonly Database database; - public ListController(Database database) { + public ListController(Database database) + { this.database = database; } #region Levels + #region Level Queue (lolcatftw) [HttpGet("slots/lolcatftw/{username}")] - public IActionResult GetLevelQueue(string username) { - IEnumerable queuedLevels = new Database().QueuedLevels - .Include(q => q.User) + public IActionResult GetLevelQueue(string username) + { + IEnumerable queuedLevels = new Database().QueuedLevels.Include + (q => q.User) .Include(q => q.Slot) .Include(q => q.Slot.Location) .Where(q => q.User.Username == username) @@ -36,30 +41,36 @@ namespace LBPUnion.ProjectLighthouse.Controllers { } [HttpPost("lolcatftw/add/user/{id:int}")] - public async Task AddQueuedLevel(int id) { + public async Task AddQueuedLevel(int id) + { User? user = await this.database.UserFromRequest(this.Request); - if(user == null) return this.StatusCode(403, ""); + if (user == null) return this.StatusCode(403, ""); QueuedLevel queuedLevel = await this.database.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); - if(queuedLevel != null) return this.Ok(); + if (queuedLevel != null) return this.Ok(); - this.database.QueuedLevels.Add(new QueuedLevel { - SlotId = id, - UserId = user.UserId, - }); + this.database.QueuedLevels.Add + ( + new QueuedLevel + { + SlotId = id, + UserId = user.UserId, + } + ); await this.database.SaveChangesAsync(); return this.Ok(); } - + [HttpPost("lolcatftw/remove/user/{id:int}")] - public async Task RemoveQueuedLevel(int id) { + public async Task RemoveQueuedLevel(int id) + { User? user = await this.database.UserFromRequest(this.Request); - if(user == null) return this.StatusCode(403, ""); + if (user == null) return this.StatusCode(403, ""); QueuedLevel queuedLevel = await this.database.QueuedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); - if(queuedLevel != null) this.database.QueuedLevels.Remove(queuedLevel); + if (queuedLevel != null) this.database.QueuedLevels.Remove(queuedLevel); await this.database.SaveChangesAsync(); @@ -71,9 +82,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers { #region Hearted Levels [HttpGet("favouriteSlots/{username}")] - public IActionResult GetFavouriteSlots(string username) { - IEnumerable heartedLevels = new Database().HeartedLevels - .Include(q => q.User) + public IActionResult GetFavouriteSlots(string username) + { + IEnumerable heartedLevels = new Database().HeartedLevels.Include + (q => q.User) .Include(q => q.Slot) .Include(q => q.Slot.Location) .Include(q => q.Slot.Creator) @@ -86,17 +98,22 @@ namespace LBPUnion.ProjectLighthouse.Controllers { } [HttpPost("favourite/slot/user/{id:int}")] - public async Task AddFavouriteSlot(int id) { + public async Task AddFavouriteSlot(int id) + { User? user = await this.database.UserFromRequest(this.Request); - if(user == null) return this.StatusCode(403, ""); + if (user == null) return this.StatusCode(403, ""); HeartedLevel heartedLevel = await this.database.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); - if(heartedLevel != null) return this.Ok(); + if (heartedLevel != null) return this.Ok(); - this.database.HeartedLevels.Add(new HeartedLevel { - SlotId = id, - UserId = user.UserId, - }); + this.database.HeartedLevels.Add + ( + new HeartedLevel + { + SlotId = id, + UserId = user.UserId, + } + ); await this.database.SaveChangesAsync(); @@ -104,12 +121,13 @@ namespace LBPUnion.ProjectLighthouse.Controllers { } [HttpPost("unfavourite/slot/user/{id:int}")] - public async Task RemoveFavouriteSlot(int id) { + public async Task RemoveFavouriteSlot(int id) + { User? user = await this.database.UserFromRequest(this.Request); - if(user == null) return this.StatusCode(403, ""); + if (user == null) return this.StatusCode(403, ""); HeartedLevel heartedLevel = await this.database.HeartedLevels.FirstOrDefaultAsync(q => q.UserId == user.UserId && q.SlotId == id); - if(heartedLevel != null) this.database.HeartedLevels.Remove(heartedLevel); + if (heartedLevel != null) this.database.HeartedLevels.Remove(heartedLevel); await this.database.SaveChangesAsync(); @@ -117,16 +135,16 @@ namespace LBPUnion.ProjectLighthouse.Controllers { } #endregion + #endregion Levels #region Users - - [HttpGet("favouriteUsers/{username}")] - public IActionResult GetFavouriteUsers(string username) { - IEnumerable heartedProfiles = new Database().HeartedProfiles - .Include(q => q.User) + public IActionResult GetFavouriteUsers(string username) + { + IEnumerable heartedProfiles = new Database().HeartedProfiles.Include + (q => q.User) .Include(q => q.HeartedUser) .Include(q => q.HeartedUser.Location) .Where(q => q.User.Username == username) @@ -136,42 +154,46 @@ namespace LBPUnion.ProjectLighthouse.Controllers { return this.Ok(LbpSerializer.TaggedStringElement("favouriteUsers", response, "total", 1)); } - + [HttpPost("favourite/user/{username}")] - public async Task AddFavouriteUser(string username) { + public async Task AddFavouriteUser(string username) + { User? user = await this.database.UserFromRequest(this.Request); - if(user == null) return this.StatusCode(403, ""); + if (user == null) return this.StatusCode(403, ""); - User? heartedUser = await this.database.Users - .FirstOrDefaultAsync(u => u.Username == username); - if(heartedUser == null) return this.NotFound(); + User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); + if (heartedUser == null) return this.NotFound(); - HeartedProfile heartedProfile = await this.database.HeartedProfiles - .FirstOrDefaultAsync(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); - if(heartedProfile != null) return this.Ok(); + HeartedProfile heartedProfile = await this.database.HeartedProfiles.FirstOrDefaultAsync + (q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); + if (heartedProfile != null) return this.Ok(); - this.database.HeartedProfiles.Add(new HeartedProfile { - HeartedUserId = heartedUser.UserId, - UserId = user.UserId, - }); + this.database.HeartedProfiles.Add + ( + new HeartedProfile + { + HeartedUserId = heartedUser.UserId, + UserId = user.UserId, + } + ); await this.database.SaveChangesAsync(); return this.Ok(); } - + [HttpPost("unfavourite/user/{username}")] - public async Task RemoveFavouriteUser(string username) { + public async Task RemoveFavouriteUser(string username) + { User? user = await this.database.UserFromRequest(this.Request); - if(user == null) return this.StatusCode(403, ""); + if (user == null) return this.StatusCode(403, ""); - User? heartedUser = await this.database.Users - .FirstOrDefaultAsync(u => u.Username == username); - if(heartedUser == null) return this.NotFound(); + User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.Username == username); + if (heartedUser == null) return this.NotFound(); - HeartedProfile heartedProfile = await this.database.HeartedProfiles - .FirstOrDefaultAsync(q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); - if(heartedProfile != null) this.database.HeartedProfiles.Remove(heartedProfile); + HeartedProfile heartedProfile = await this.database.HeartedProfiles.FirstOrDefaultAsync + (q => q.UserId == user.UserId && q.HeartedUserId == heartedUser.UserId); + if (heartedProfile != null) this.database.HeartedProfiles.Remove(heartedProfile); await this.database.SaveChangesAsync(); @@ -179,5 +201,6 @@ namespace LBPUnion.ProjectLighthouse.Controllers { } #endregion + } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/LoginController.cs b/ProjectLighthouse/Controllers/LoginController.cs index 491c1bfb..057771c2 100644 --- a/ProjectLighthouse/Controllers/LoginController.cs +++ b/ProjectLighthouse/Controllers/LoginController.cs @@ -24,25 +24,30 @@ namespace LBPUnion.ProjectLighthouse.Controllers { string body = await new StreamReader(this.Request.Body).ReadToEndAsync(); - LoginData loginData; + LoginData? loginData; try { loginData = LoginData.CreateFromString(body); } catch { - return this.BadRequest(); + loginData = null; } + if (loginData == null) return this.BadRequest(); + Token? token = await this.database.AuthenticateUser(loginData); if (token == null) return this.StatusCode(403, ""); - return this.Ok(new LoginResult - { - AuthTicket = "MM_AUTH=" + token.UserToken, - LbpEnvVer = ServerSettings.ServerName, - }.Serialize()); + return this.Ok + ( + new LoginResult + { + AuthTicket = "MM_AUTH=" + token.UserToken, + LbpEnvVer = ServerSettings.ServerName, + }.Serialize() + ); } } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/MatchController.cs b/ProjectLighthouse/Controllers/MatchController.cs index e4ccb354..81fe97e0 100644 --- a/ProjectLighthouse/Controllers/MatchController.cs +++ b/ProjectLighthouse/Controllers/MatchController.cs @@ -40,10 +40,10 @@ namespace LBPUnion.ProjectLighthouse.Controllers string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); if (bodyString.Contains("FindBestRoom")) - { - return this.Ok( - "[{\"StatusCode\":200},{\"Players\":[{\"PlayerId\":\"literally1984\",\"matching_res\":0},{\"PlayerId\":\"jvyden\",\"matching_res\":1}],\"Slots\":[[5,0]],\"RoomState\":\"E_ROOM_IN_POD\",\"HostMood\":\"E_MOOD_EVERYONE\",\"LevelCompletionEstimate\":0,\"PassedNoJoinPoint\":0,\"MoveConnected\":false,\"Location\":[\"127.0.0.1\"],\"BuildVersion\":289,\"Language\":1,\"FirstSeenTimestamp\":1427331263756,\"LastSeenTimestamp\":1635112546000,\"GameId\":1,\"NatType\":2,\"Friends\":[],\"Blocked\":[],\"RecentlyLeft\":[],\"FailedJoin\":[]}]"); - } + return this.Ok + ( + "[{\"StatusCode\":200},{\"Players\":[{\"PlayerId\":\"literally1984\",\"matching_res\":0},{\"PlayerId\":\"jvyden\",\"matching_res\":1}],\"Slots\":[[5,0]],\"RoomState\":\"E_ROOM_IN_POD\",\"HostMood\":\"E_MOOD_EVERYONE\",\"LevelCompletionEstimate\":0,\"PassedNoJoinPoint\":0,\"MoveConnected\":false,\"Location\":[\"127.0.0.1\"],\"BuildVersion\":289,\"Language\":1,\"FirstSeenTimestamp\":1427331263756,\"LastSeenTimestamp\":1635112546000,\"GameId\":1,\"NatType\":2,\"Friends\":[],\"Blocked\":[],\"RecentlyLeft\":[],\"FailedJoin\":[]}]" + ); if (bodyString[0] != '[') return this.BadRequest(); @@ -52,7 +52,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers { matchData = MatchHelper.Deserialize(bodyString); } - catch (Exception e) + catch(Exception e) { Logger.Log("Exception while parsing MatchData: " + e); Logger.Log("Data: " + bodyString); @@ -66,8 +66,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers #region Update LastMatch - LastMatch? lastMatch = await this.database.LastMatches - .Where(l => l.UserId == user.UserId).FirstOrDefaultAsync(); + LastMatch? lastMatch = await this.database.LastMatches.Where(l => l.UserId == user.UserId).FirstOrDefaultAsync(); // below makes it not look like trash // ReSharper disable once ConvertIfStatementToNullCoalescingExpression @@ -88,16 +87,5 @@ namespace LBPUnion.ProjectLighthouse.Controllers return this.Ok("[{\"StatusCode\":200}]"); } - - [HttpGet("playersInPodCount")] - [HttpGet("totalPlayerCount")] - public async Task TotalPlayerCount() - { - int recentMatches = await this.database.LastMatches - .Where(l => TimestampHelper.Timestamp - l.Timestamp < 60) - .CountAsync(); - - return this.Ok(recentMatches.ToString()); - } } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/MessageController.cs b/ProjectLighthouse/Controllers/MessageController.cs index 41c3c77f..193443c5 100644 --- a/ProjectLighthouse/Controllers/MessageController.cs +++ b/ProjectLighthouse/Controllers/MessageController.cs @@ -1,5 +1,8 @@ using System.IO; using System.Threading.Tasks; +using Kettu; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Mvc; @@ -22,34 +25,36 @@ namespace LBPUnion.ProjectLighthouse.Controllers { User user = await this.database.UserFromRequest(this.Request); return user == null - ? this.Forbid() - : this.Ok($"You are now logged in as user {user.Username} (id {user.UserId}).\n" + - "This is a private testing instance. Please do not make anything public for now, and keep in mind security isn't as tight as a full release would."); + ? this.StatusCode(403, "") + : this.Ok + ( + $"You are now logged in as user {user.Username} (id {user.UserId}).\n" + + // ReSharper disable once UnreachableCode + (EulaHelper.ShowPrivateInstanceNotice ? "\n" + EulaHelper.PrivateInstanceNotice : "") + + "\n" + + $"{EulaHelper.License}\n" + ); } [HttpGet("announce")] - public async Task Announce() - { - User user = await this.database.UserFromRequest(this.Request); - return user == null - ? this.Forbid() - : this.Ok($"You are now logged in as user {user.Username} (id {user.UserId}).\n" + - "This is a private testing instance. Please do not make anything public for now, and keep in mind security isn't as tight as a full release would."); - } + public IActionResult Announce() => this.Ok(""); [HttpGet("notification")] - public IActionResult Notification() - { - return this.Ok(); - } - + public IActionResult Notification() => this.Ok(); /// - /// 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. /// [HttpPost("filter")] public async Task Filter() { - return this.Ok(await new StreamReader(this.Request.Body).ReadToEndAsync()); + User user = await this.database.UserFromRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); + + string loggedText = await new StreamReader(this.Request.Body).ReadToEndAsync(); + + Logger.Log($"{user.Username}: {loggedText}", LoggerLevelFilter.Instance); + return this.Ok(loggedText); } } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/NewsController.cs b/ProjectLighthouse/Controllers/NewsController.cs index a3f118a3..ee0342d2 100644 --- a/ProjectLighthouse/Controllers/NewsController.cs +++ b/ProjectLighthouse/Controllers/NewsController.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types.News; using Microsoft.AspNetCore.Mvc; @@ -13,20 +12,24 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpGet] public IActionResult Get() { - string newsEntry = LbpSerializer.StringElement("item", new NewsEntry - { - Category = "no_category", - Summary = "test summary", - Image = new NewsImage + string newsEntry = LbpSerializer.StringElement + ( + "item", + new NewsEntry { - Hash = "4947269c5f7061b27225611ee58a9a91a8031bbe", - Alignment = "right", - }, - Id = 1, - Title = "Test Title", - Text = "Test Text", - Date = 1348755214000, - }.Serialize()); + 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)); } diff --git a/ProjectLighthouse/Controllers/PublishController.cs b/ProjectLighthouse/Controllers/PublishController.cs index 8136e10a..4efb4176 100644 --- a/ProjectLighthouse/Controllers/PublishController.cs +++ b/ProjectLighthouse/Controllers/PublishController.cs @@ -10,64 +10,102 @@ using LBPUnion.ProjectLighthouse.Types.Profiles; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] [Produces("text/xml")] - public class PublishController : ControllerBase { + public class PublishController : ControllerBase + { private readonly Database database; - - public PublishController(Database database) { + + public PublishController(Database database) + { this.database = database; } /// - /// Endpoint the game uses to verify that the level is compatible (?) + /// Endpoint the game uses to check what resources need to be uploaded and if the level can be uploaded /// [HttpPost("startPublish")] - public async Task StartPublish() { - Slot slot = await this.GetSlotFromBody(); - if(slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded + public async Task StartPublish() + { + User user = await this.database.UserFromRequest(this.Request); + if (user == null) return this.StatusCode(403, ""); - string resources = slot.Resources - .Where(hash => !FileHelper.ResourceExists(hash)) - .Aggregate("", (current, hash) => - current + LbpSerializer.StringElement("resource", hash)); + Slot slot = await this.GetSlotFromBody(); + if (slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded + + // Republish logic + 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(); + } + + 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")); } /// - /// Endpoint actually used to publish a level + /// Endpoint actually used to publish a level /// [HttpPost("publish")] - public async Task Publish() { + public async Task Publish() + { User user = await this.database.UserFromRequest(this.Request); - if(user == null) return this.StatusCode(403, ""); - + if (user == null) return this.StatusCode(403, ""); + Slot slot = await this.GetSlotFromBody(); + // Republish logic + if (slot.SlotId != 0) + { + Slot oldSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slot.SlotId); + if (oldSlot == null) return this.NotFound(); + if (oldSlot.CreatorId != user.UserId) return this.BadRequest(); + + oldSlot.Location.X = slot.Location.X; + oldSlot.Location.Y = slot.Location.Y; + + slot.CreatorId = oldSlot.CreatorId; + slot.LocationId = oldSlot.LocationId; + slot.SlotId = oldSlot.SlotId; + slot.FirstUploaded = oldSlot.FirstUploaded; + slot.LastUpdated = TimeHelper.UnixTimeMilliseconds(); + + 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 = 0, - Y = 0, + 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(); this.database.Slots.Add(slot); await this.database.SaveChangesAsync(); - + return this.Ok(slot.Serialize()); } [HttpPost("unpublish/{id:int}")] - public async Task Unpublish(int id) { - Slot slot = await this.database.Slots - .Include(s => s.Location) - .FirstOrDefaultAsync(s => s.SlotId == id); + public async Task Unpublish(int id) + { + Slot slot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id); this.database.Locations.Remove(slot.Location); this.database.Slots.Remove(slot); @@ -76,8 +114,9 @@ namespace LBPUnion.ProjectLighthouse.Controllers { return this.Ok(); } - - public async Task GetSlotFromBody() { + + public async Task GetSlotFromBody() + { this.Request.Body.Position = 0; string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); diff --git a/ProjectLighthouse/Controllers/ResourcesController.cs b/ProjectLighthouse/Controllers/ResourcesController.cs index c7726935..dcb1a4d8 100644 --- a/ProjectLighthouse/Controllers/ResourcesController.cs +++ b/ProjectLighthouse/Controllers/ResourcesController.cs @@ -18,10 +18,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers public class ResourcesController : ControllerBase { [HttpPost("showModerated")] - public IActionResult ShowModerated() - { - return this.Ok(LbpSerializer.BlankElement("resources")); - } + public IActionResult ShowModerated() => this.Ok(LbpSerializer.BlankElement("resources")); [HttpPost("filterResources")] [HttpPost("showNotUploaded")] @@ -30,14 +27,13 @@ namespace LBPUnion.ProjectLighthouse.Controllers string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); XmlSerializer serializer = new(typeof(ResourceList)); - ResourceList resourceList = (ResourceList) serializer.Deserialize(new StringReader(bodyString)); + 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)); + string resources = resourceList.Resources.Where + (s => !FileHelper.ResourceExists(s)) + .Aggregate("", (current, hash) => current + LbpSerializer.StringElement("resource", hash)); return this.Ok(LbpSerializer.StringElement("resources", resources)); } @@ -47,10 +43,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers { string path = FileHelper.GetResourcePath(hash); - if (FileHelper.ResourceExists(hash)) - { - return this.File(IOFile.OpenRead(path), "application/octet-stream"); - } + if (FileHelper.ResourceExists(hash)) return this.File(IOFile.OpenRead(path), "application/octet-stream"); return this.NotFound(); } @@ -68,7 +61,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers if (FileHelper.ResourceExists(hash)) this.Ok(); // no reason to fail if it's already uploaded Logger.Log($"Processing resource upload (hash: {hash})"); - LbpFile file = new(await BinaryHelper.ReadFromPipeReader(Request.BodyReader)); + LbpFile file = new(await BinaryHelper.ReadFromPipeReader(this.Request.BodyReader)); if (!FileHelper.IsFileSafe(file)) return this.UnprocessableEntity(); diff --git a/ProjectLighthouse/Controllers/SearchController.cs b/ProjectLighthouse/Controllers/SearchController.cs index adaced48..573d3480 100644 --- a/ProjectLighthouse/Controllers/SearchController.cs +++ b/ProjectLighthouse/Controllers/SearchController.cs @@ -6,18 +6,24 @@ using LBPUnion.ProjectLighthouse.Types.Levels; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] [Produces("text/xml")] - public class SearchController : ControllerBase { + public class SearchController : ControllerBase + { private readonly Database database; - public SearchController(Database database) { + public SearchController(Database database) + { this.database = database; } [HttpGet("slots/search")] - public async Task SearchSlots([FromQuery] string query) { + public async Task SearchSlots([FromQuery] string query) + { + if (query == null) return this.BadRequest(); + query = query.ToLower(); string[] keywords = query.Split(" "); @@ -26,16 +32,16 @@ namespace LBPUnion.ProjectLighthouse.Controllers { .Include(s => s.Creator) .Include(s => s.Location) .Where(s => s.SlotId >= 0); // dumb query to conv into IQueryable - + // ReSharper disable once LoopCanBeConvertedToQuery - foreach(string keyword in keywords) { - dbQuery = dbQuery.Where(s => - s.Name.ToLower().Contains(keyword) || - s.Description.ToLower().Contains(keyword) || - s.Creator.Username.ToLower().Contains(keyword) || - s.SlotId.ToString().Equals(keyword) + foreach (string keyword in keywords) + dbQuery = dbQuery.Where + ( + s => s.Name.ToLower().Contains(keyword) || + s.Description.ToLower().Contains(keyword) || + s.Creator.Username.ToLower().Contains(keyword) || + s.SlotId.ToString().Equals(keyword) ); - } List slots = await dbQuery.ToListAsync(); string response = slots.Aggregate("", (current, slot) => current + slot.Serialize()); diff --git a/ProjectLighthouse/Controllers/SlotsController.cs b/ProjectLighthouse/Controllers/SlotsController.cs index d5c0f818..ede1ae56 100644 --- a/ProjectLighthouse/Controllers/SlotsController.cs +++ b/ProjectLighthouse/Controllers/SlotsController.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Serialization; @@ -5,35 +6,54 @@ using LBPUnion.ProjectLighthouse.Types.Levels; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Controllers { +namespace LBPUnion.ProjectLighthouse.Controllers +{ [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] [Produces("text/xml")] - public class SlotsController : ControllerBase { + public class SlotsController : ControllerBase + { private readonly Database database; - public SlotsController(Database database) { + public SlotsController(Database database) + { this.database = database; } [HttpGet("slots/by")] - public IActionResult SlotsBy([FromQuery] string u) { - string response = Enumerable.Aggregate( - this.database.Slots - .Include(s => s.Creator) - .Include(s => s.Location) - .Where(s => s.Creator.Username == u) - , string.Empty, (current, slot) => current + slot.Serialize()); + public IActionResult SlotsBy([FromQuery] string u) + { + string response = Enumerable.Aggregate + ( + this.database.Slots.Include(s => s.Creator).Include(s => s.Location).Where(s => s.Creator.Username == u), + string.Empty, + (current, slot) => current + slot.Serialize() + ); return this.Ok(LbpSerializer.TaggedStringElement("slots", response, "total", 1)); } - - public async Task SUser(int id) { - Slot slot = await this.database.Slots - .Include(s => s.Creator) - .Include(s => s.Location) - .FirstOrDefaultAsync(s => s.SlotId == id); + + [HttpGet("s/user/{id:int}")] + public async Task SUser(int id) + { + Slot slot = await this.database.Slots.Include(s => s.Creator).Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == id); + + if (slot == null) return this.NotFound(); return this.Ok(slot.Serialize()); } + + [HttpGet("slots")] + public IActionResult NewestSlots([FromQuery] int pageStart, [FromQuery] int pageSize) + { + IQueryable slots = this.database.Slots.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("slots", response, "hint_start", pageStart + Math.Min(pageSize, 30))); + } } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/StatisticsController.cs b/ProjectLighthouse/Controllers/StatisticsController.cs new file mode 100644 index 00000000..0bacbd4a --- /dev/null +++ b/ProjectLighthouse/Controllers/StatisticsController.cs @@ -0,0 +1,43 @@ +using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Serialization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Controllers +{ + [ApiController] + [Route("LITTLEBIGPLANETPS3_XML/")] + [Produces("text/plain")] + public class StatisticsController : ControllerBase + { + private readonly Database database; + public StatisticsController(Database database) + { + this.database = database; + } + + [HttpGet("playersInPodCount")] + [HttpGet("totalPlayerCount")] + public async Task TotalPlayerCount() + { + int recentMatches = await this.database.LastMatches.Where(l => TimestampHelper.Timestamp - l.Timestamp < 60).CountAsync(); + + return this.Ok(recentMatches.ToString()); + } + + [HttpGet("planetStats")] + public async Task PlanetStats() + { + int totalSlotCount = await this.database.Slots.CountAsync(); + const int mmPicksCount = 0; + + return this.Ok + ( + LbpSerializer.StringElement + ("planetStats", LbpSerializer.StringElement("totalSlotCount", totalSlotCount) + LbpSerializer.StringElement("mmPicksCount", mmPicksCount)) + ); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/StoreController.cs b/ProjectLighthouse/Controllers/StoreController.cs index 1fe7862b..7c68be02 100644 --- a/ProjectLighthouse/Controllers/StoreController.cs +++ b/ProjectLighthouse/Controllers/StoreController.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; namespace LBPUnion.ProjectLighthouse.Controllers { @@ -9,9 +8,6 @@ namespace LBPUnion.ProjectLighthouse.Controllers public class StoreController : Controller { [HttpGet("promotions")] - public IActionResult Promotions() - { - return Ok(); - } + public IActionResult Promotions() => this.Ok(); } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/UserController.cs b/ProjectLighthouse/Controllers/UserController.cs index 666f7d87..265d9126 100644 --- a/ProjectLighthouse/Controllers/UserController.cs +++ b/ProjectLighthouse/Controllers/UserController.cs @@ -25,25 +25,18 @@ namespace LBPUnion.ProjectLighthouse.Controllers [HttpGet("user/{username}")] public async Task GetUser(string username) { - User user = await this.database.Users - .Include(u => u.Location) - .FirstOrDefaultAsync(u => u.Username == username); + User user = await this.database.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.Username == username); if (user == null) return this.NotFound(); + return this.Ok(user.Serialize()); } - [HttpGet("user/{username}/playlists")] - public IActionResult GetUserPlaylists(string username) - { - return this.Ok(); - } + [HttpGet("users")] + public async Task GetUserAlt([FromQuery] string u) => await this.GetUser(u); -// [HttpPost("user/{username}")] -// public async Task CreateUser(string username) { -// await new Database().CreateUser(username); -// return await GetUser(username); -// } + [HttpGet("user/{username}/playlists")] + public IActionResult GetUserPlaylists(string username) => this.Ok(); [HttpPost("updateUser")] public async Task UpdateUser() @@ -77,11 +70,8 @@ namespace LBPUnion.ProjectLighthouse.Controllers // 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 - path = new(); // you can think of this as a file path in the XML, like -> -> - while (await reader.ReadAsync()) - { - // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + List path = new(); // you can think of this as a file path in the XML, like -> -> + while (await reader.ReadAsync()) // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault switch (reader.NodeType) { case XmlNodeType.Element: @@ -97,21 +87,12 @@ namespace LBPUnion.ProjectLighthouse.Controllers } case "location": { - locationChanged = - true; // if we're here then we're probably about to change the 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()); - } - + 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": @@ -131,14 +112,12 @@ namespace LBPUnion.ProjectLighthouse.Controllers path.RemoveAt(path.Count - 1); 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.Where(l => l.Id == user.LocationId) - .FirstOrDefaultAsync(); // find the location in the database again + Location l = await this.database.Locations.Where(l => l.Id == user.LocationId).FirstOrDefaultAsync(); // find the location in the database again // set the location in the database to the one we modified above l.X = user.Location.X; @@ -147,8 +126,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers // 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 + if (this.database.ChangeTracker.HasChanges()) await this.database.SaveChangesAsync(); // save the user to the database if we changed anything return this.Ok(); } } diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 2bb4714c..c80e6c50 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -23,16 +23,13 @@ namespace LBPUnion.ProjectLighthouse public DbSet LastMatches { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseMySql( - ServerSettings.DbConnectionString, - MySqlServerVersion.LatestSupportedServerVersion - ); + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options.UseMySql(ServerSettings.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion); public async Task CreateUser(string username) { User user; - if ((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) - return user; + if ((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) return user; Location l = new(); // store to get id after submitting this.Locations.Add(l); // add to table @@ -51,12 +48,11 @@ namespace LBPUnion.ProjectLighthouse return user; } -#nullable enable + #nullable enable public async Task AuthenticateUser(LoginData loginData) { // TODO: don't use psn name to authenticate - User user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username) - ?? await this.CreateUser(loginData.Username); + User user = await this.Users.FirstOrDefaultAsync(u => u.Username == loginData.Username) ?? await this.CreateUser(loginData.Username); Token token = new() { @@ -74,20 +70,16 @@ namespace LBPUnion.ProjectLighthouse { Token? token = await this.Tokens.FirstOrDefaultAsync(t => t.UserToken == authToken); if (token == null) return null; - return await this.Users - .Include(u => u.Location) - .FirstOrDefaultAsync(u => u.UserId == token.UserId); + + return await this.Users.Include(u => u.Location).FirstOrDefaultAsync(u => u.UserId == token.UserId); } public async Task UserFromRequest(HttpRequest request) { - if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) - { - return null; - } + if (!request.Cookies.TryGetValue("MM_AUTH", out string? mmAuth) || mmAuth == null) return null; return await this.UserFromAuthToken(mmAuth); } -#nullable disable + #nullable disable } } \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/AllowSynchronousIOAttribute.cs b/ProjectLighthouse/Helpers/AllowSynchronousIOAttribute.cs index 8cf82612..1054f1a7 100644 --- a/ProjectLighthouse/Helpers/AllowSynchronousIOAttribute.cs +++ b/ProjectLighthouse/Helpers/AllowSynchronousIOAttribute.cs @@ -2,17 +2,20 @@ using System; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Filters; -namespace LBPUnion.ProjectLighthouse.Helpers { +namespace LBPUnion.ProjectLighthouse.Helpers +{ // Yoinked from https://stackoverflow.com/a/68530667 // Thanks to T-moty! /// - /// Allows synchronous stream operations for this request. + /// Allows synchronous stream operations for this request. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public class AllowSynchronousIoAttribute : ActionFilterAttribute { - public override void OnResultExecuting(ResultExecutingContext context) { + public class AllowSynchronousIoAttribute : ActionFilterAttribute + { + public override void OnResultExecuting(ResultExecutingContext context) + { IHttpBodyControlFeature syncIoFeature = context.HttpContext.Features.Get(); - if(syncIoFeature != null) syncIoFeature.AllowSynchronousIO = true; + if (syncIoFeature != null) syncIoFeature.AllowSynchronousIO = true; } } } \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/BinaryHelper.cs b/ProjectLighthouse/Helpers/BinaryHelper.cs index 80d0b1cf..3765ef46 100644 --- a/ProjectLighthouse/Helpers/BinaryHelper.cs +++ b/ProjectLighthouse/Helpers/BinaryHelper.cs @@ -6,52 +6,57 @@ using System.IO.Pipelines; using System.Text; using System.Threading.Tasks; -namespace LBPUnion.ProjectLighthouse.Helpers { - public static class BinaryHelper { - public static string ReadString(BinaryReader reader) { +namespace LBPUnion.ProjectLighthouse.Helpers +{ + public static class BinaryHelper + { + public static string ReadString(BinaryReader reader) + { List readBytes = new(); byte readByte; - do { - readBytes.Add(readByte = reader.ReadByte()); - } while(readByte != 0x00); + do readBytes.Add(readByte = reader.ReadByte()); + while (readByte != 0x00); return Encoding.UTF8.GetString(readBytes.ToArray()); } - public static void ReadUntilByte(BinaryReader reader, byte byteToReadTo) { + public static void ReadUntilByte(BinaryReader reader, byte byteToReadTo) + { byte readByte; - do { - readByte = reader.ReadByte(); - } while(readByte != byteToReadTo); + do readByte = reader.ReadByte(); + while (readByte != byteToReadTo); } - public static byte[] ReadLastBytes(BinaryReader reader, int count, bool restoreOldPosition = true) { + public static byte[] ReadLastBytes(BinaryReader reader, int count, bool restoreOldPosition = true) + { long oldPosition = reader.BaseStream.Position; - if(reader.BaseStream.Length < count) return Array.Empty(); - + if (reader.BaseStream.Length < count) return Array.Empty(); + reader.BaseStream.Position = reader.BaseStream.Length - count; byte[] data = reader.ReadBytes(count); - - if(restoreOldPosition) reader.BaseStream.Position = oldPosition; + + 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 ReadFromPipeReader(PipeReader reader) { + public static async Task ReadFromPipeReader(PipeReader reader) + { List data = new(); - while(true) { + while (true) + { ReadResult readResult = await reader.ReadAsync(); ReadOnlySequence buffer = readResult.Buffer; - if(readResult.IsCompleted && buffer.Length > 0) data.AddRange(buffer.ToArray()); - + if (readResult.IsCompleted && buffer.Length > 0) data.AddRange(buffer.ToArray()); + reader.AdvanceTo(buffer.Start, buffer.End); - - if(readResult.IsCompleted) break; + + if (readResult.IsCompleted) break; } return data.ToArray(); diff --git a/ProjectLighthouse/Helpers/EulaHelper.cs b/ProjectLighthouse/Helpers/EulaHelper.cs new file mode 100644 index 00000000..cfaa3940 --- /dev/null +++ b/ProjectLighthouse/Helpers/EulaHelper.cs @@ -0,0 +1,24 @@ +namespace LBPUnion.ProjectLighthouse.Helpers +{ + public static class EulaHelper + { + public const string License = @" +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see ."; + + public const string PrivateInstanceNotice = @"This server is a private testing instance. +Please do not make anything public for now, and keep in mind security isn't as tight as a full release would."; + + public const bool ShowPrivateInstanceNotice = false; + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/Extensions/ExceptionExtensions.cs b/ProjectLighthouse/Helpers/Extensions/ExceptionExtensions.cs index 0ea759a3..5a119dbc 100644 --- a/ProjectLighthouse/Helpers/Extensions/ExceptionExtensions.cs +++ b/ProjectLighthouse/Helpers/Extensions/ExceptionExtensions.cs @@ -3,17 +3,23 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -namespace LBPUnion.ProjectLighthouse.Helpers.Extensions { +namespace LBPUnion.ProjectLighthouse.Helpers.Extensions +{ // https://stackoverflow.com/a/8039737 - public static class ExceptionExtensions { - public static string ToDetailedException(this Exception exception) { + public static class ExceptionExtensions + { + public static string ToDetailedException(this Exception exception) + { PropertyInfo[] properties = exception.GetType().GetProperties(); - - IEnumerable fields = properties - .Select(property => new { - property.Name, - Value = property.GetValue(exception, null), - }) + + IEnumerable fields = properties.Select + ( + property => new + { + property.Name, + Value = property.GetValue(exception, null), + } + ) .Select(x => $"{x.Name} = {(x.Value != null ? x.Value.ToString() : string.Empty)}"); return string.Join("\n", fields); diff --git a/ProjectLighthouse/Helpers/FileHelper.cs b/ProjectLighthouse/Helpers/FileHelper.cs index 8f9e0f44..fbb23117 100644 --- a/ProjectLighthouse/Helpers/FileHelper.cs +++ b/ProjectLighthouse/Helpers/FileHelper.cs @@ -4,17 +4,22 @@ using System.Linq; using System.Text; using LBPUnion.ProjectLighthouse.Types.Files; -namespace LBPUnion.ProjectLighthouse.Helpers { - public static class FileHelper { +namespace LBPUnion.ProjectLighthouse.Helpers +{ + 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) { - if(file.FileType == LbpFileType.Unknown) file.FileType = DetermineFileType(file.Data); - - return file.FileType switch { + + public static bool IsFileSafe(LbpFile file) + { + if (file.FileType == LbpFileType.Unknown) file.FileType = DetermineFileType(file.Data); + + return file.FileType switch + { LbpFileType.FileArchive => false, + LbpFileType.Painting => true, LbpFileType.Unknown => false, LbpFileType.Texture => true, LbpFileType.Script => false, @@ -29,16 +34,19 @@ namespace LBPUnion.ProjectLighthouse.Helpers { }; } - public static LbpFileType DetermineFileType(byte[] data) { + public static LbpFileType DetermineFileType(byte[] data) + { using MemoryStream ms = new(data); using BinaryReader reader = new(ms); string footer = Encoding.ASCII.GetString(BinaryHelper.ReadLastBytes(reader, 4)); - if(footer == "FARC") return LbpFileType.FileArchive; + if (footer == "FARC") return LbpFileType.FileArchive; byte[] header = reader.ReadBytes(3); - return Encoding.ASCII.GetString(header) switch { + return Encoding.ASCII.GetString(header) switch + { + "PTG" => LbpFileType.Painting, "TEX" => LbpFileType.Texture, "FSH" => LbpFileType.Script, "VOP" => LbpFileType.Voice, @@ -50,8 +58,9 @@ namespace LBPUnion.ProjectLighthouse.Helpers { public static bool ResourceExists(string hash) => File.Exists(GetResourcePath(hash)); - public static void EnsureDirectoryCreated(string path) { - if(!Directory.Exists(path)) Directory.CreateDirectory(path ?? throw new ArgumentNullException(nameof(path))); + 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(); diff --git a/ProjectLighthouse/Helpers/HashHelper.cs b/ProjectLighthouse/Helpers/HashHelper.cs index 865a04f3..ffda6e17 100644 --- a/ProjectLighthouse/Helpers/HashHelper.cs +++ b/ProjectLighthouse/Helpers/HashHelper.cs @@ -6,32 +6,22 @@ using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; -namespace LBPUnion.ProjectLighthouse.Helpers { +namespace LBPUnion.ProjectLighthouse.Helpers +{ [SuppressMessage("ReSharper", "UnusedMember.Global")] - public static class HashHelper { + public static class HashHelper + { // private static readonly SHA1 sha1 = SHA1.Create(); private static readonly SHA256 sha256 = SHA256.Create(); private static readonly Random random = new(); - #region Hash Functions - public static string Sha256Hash(string str) => Sha256Hash(Encoding.UTF8.GetBytes(str)); - - public static string Sha256Hash(byte[] bytes) { - byte[] hash = sha256.ComputeHash(bytes); - return Encoding.UTF8.GetString(hash, 0, hash.Length); - } - - 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 - /// - /// Generates a specified amount of random bytes in an array. + /// Generates a specified amount of random bytes in an array. /// /// The amount of bytes to generate. /// The bytes generated - public static IEnumerable GenerateRandomBytes(int count) { + public static IEnumerable GenerateRandomBytes(int count) + { byte[] b = new byte[count]; random.NextBytes(b); @@ -39,34 +29,31 @@ namespace LBPUnion.ProjectLighthouse.Helpers { } /// - /// Generates a random SHA256 & BCrypted token + /// Generates a random SHA256 & BCrypted token /// /// The token as a string. - public static string GenerateAuthToken() { - byte[] bytes = (byte[]) GenerateRandomBytes(256); + public static string GenerateAuthToken() + { + byte[] bytes = (byte[])GenerateRandomBytes(256); return BCryptHash(Sha256Hash(bytes)); } - - public static async Task ComputeDigest(string path, string authCookie, Stream body, - string digestKey) + + public static async Task ComputeDigest(string path, string authCookie, Stream body, string digestKey) { - MemoryStream memoryStream = new MemoryStream(); + MemoryStream memoryStream = new(); byte[] pathBytes = Encoding.UTF8.GetBytes(path); - byte[] cookieBytes = string.IsNullOrEmpty(authCookie) - ? Array.Empty() - : Encoding.UTF8.GetBytes(authCookie); + byte[] cookieBytes = string.IsNullOrEmpty(authCookie) ? Array.Empty() : 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); + if (cookieBytes.Length > 0) sha1.AppendData(cookieBytes); sha1.AppendData(pathBytes); sha1.AppendData(keyBytes); @@ -75,5 +62,22 @@ namespace LBPUnion.ProjectLighthouse.Helpers { return digestString; } + + #region Hash Functions + + public static string Sha256Hash(string str) => Sha256Hash(Encoding.UTF8.GetBytes(str)); + + public static string Sha256Hash(byte[] bytes) + { + byte[] hash = sha256.ComputeHash(bytes); + return Encoding.UTF8.GetString(hash, 0, hash.Length); + } + + 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 + } } \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/TimeHelper.cs b/ProjectLighthouse/Helpers/TimeHelper.cs new file mode 100644 index 00000000..77d8757d --- /dev/null +++ b/ProjectLighthouse/Helpers/TimeHelper.cs @@ -0,0 +1,13 @@ +using System; + +namespace LBPUnion.ProjectLighthouse.Helpers +{ + public static class TimeHelper + { + public static long UnixTimeMilliseconds() => DateTimeOffset.Now.ToUnixTimeMilliseconds(); + public static long UnixTimeSeconds() => DateTimeOffset.Now.ToUnixTimeSeconds(); + } +} + +// 1397109686193 +// 1635389749454 \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/TimestampHelper.cs b/ProjectLighthouse/Helpers/TimestampHelper.cs index 62ac5d60..3a3e9d48 100644 --- a/ProjectLighthouse/Helpers/TimestampHelper.cs +++ b/ProjectLighthouse/Helpers/TimestampHelper.cs @@ -1,7 +1,9 @@ using System; -namespace LBPUnion.ProjectLighthouse.Helpers { - public static class TimestampHelper { +namespace LBPUnion.ProjectLighthouse.Helpers +{ + public static class TimestampHelper + { public static long Timestamp => (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; } } \ No newline at end of file diff --git a/ProjectLighthouse/Logging/AspNetToKettuLogger.cs b/ProjectLighthouse/Logging/AspNetToKettuLogger.cs index 944fafa5..b20ce5a7 100644 --- a/ProjectLighthouse/Logging/AspNetToKettuLogger.cs +++ b/ProjectLighthouse/Logging/AspNetToKettuLogger.cs @@ -3,32 +3,23 @@ using Kettu; using LBPUnion.ProjectLighthouse.Helpers.Extensions; using Microsoft.Extensions.Logging; -namespace LBPUnion.ProjectLighthouse.Logging { - public class AspNetToKettuLogger : ILogger { +namespace LBPUnion.ProjectLighthouse.Logging +{ + public class AspNetToKettuLogger : ILogger + { - public IDisposable BeginScope(TState state) { - return NullScope.Instance; - } + public IDisposable BeginScope(TState state) => NullScope.Instance; public bool IsEnabled(LogLevel logLevel) => true; - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - LoggerLevel loggerLevel = logLevel switch { - LogLevel.Trace => LoggerLevelAspNetTrace.Instance, - LogLevel.Debug => LoggerLevelAspNetDebug.Instance, - LogLevel.Information => LoggerLevelAspNetInformation.Instance, - LogLevel.Warning => LoggerLevelAspNetWarning.Instance, - LogLevel.Error => LoggerLevelAspNetError.Instance, - LogLevel.Critical => LoggerLevelAspNetCritical.Instance, - LogLevel.None => LoggerLevelAspNetNone.Instance, - _ => throw new ArgumentOutOfRangeException(nameof(logLevel), logLevel, null), - }; - + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + LoggerLevel loggerLevel = new LoggerLevelAspNet(logLevel); + Logger.Log(state.ToString(), loggerLevel); - if(exception == null) return; + if (exception == null) return; string[] lines = exception.ToDetailedException().Replace("\r", "").Split("\n"); - foreach(string line in lines) Logger.Log(line, loggerLevel); + foreach (string line in lines) Logger.Log(line, loggerLevel); } } } \ No newline at end of file diff --git a/ProjectLighthouse/Logging/AspNetToKettuLoggerProvider.cs b/ProjectLighthouse/Logging/AspNetToKettuLoggerProvider.cs index 557b4462..84039bfe 100644 --- a/ProjectLighthouse/Logging/AspNetToKettuLoggerProvider.cs +++ b/ProjectLighthouse/Logging/AspNetToKettuLoggerProvider.cs @@ -1,15 +1,16 @@ using System; using Microsoft.Extensions.Logging; -namespace LBPUnion.ProjectLighthouse.Logging { +namespace LBPUnion.ProjectLighthouse.Logging +{ [ProviderAlias("Kettu")] - public class AspNetToKettuLoggerProvider : ILoggerProvider, IDisposable { - public void Dispose() { + public class AspNetToKettuLoggerProvider : ILoggerProvider, IDisposable + { + public void Dispose() + { GC.SuppressFinalize(this); } - - public ILogger CreateLogger(string categoryName) { - return new AspNetToKettuLogger(); - } + + public ILogger CreateLogger(string categoryName) => new AspNetToKettuLogger(); } } \ No newline at end of file diff --git a/ProjectLighthouse/Logging/LighthouseFileLogger.cs b/ProjectLighthouse/Logging/LighthouseFileLogger.cs index 82863a72..1075dd0f 100644 --- a/ProjectLighthouse/Logging/LighthouseFileLogger.cs +++ b/ProjectLighthouse/Logging/LighthouseFileLogger.cs @@ -3,16 +3,23 @@ using System.IO; using Kettu; using LBPUnion.ProjectLighthouse.Helpers; -namespace LBPUnion.ProjectLighthouse.Logging { - public class LighthouseFileLogger : LoggerBase { +namespace LBPUnion.ProjectLighthouse.Logging +{ + public class LighthouseFileLogger : LoggerBase + { private static readonly string logsDirectory = Path.Combine(Environment.CurrentDirectory, "logs"); - + public override void Send(LoggerLine line) { FileHelper.EnsureDirectoryCreated(logsDirectory); - - File.AppendAllText(Path.Combine(logsDirectory, line.LoggerLevel + ".log"), line.LineData + "\n"); - File.AppendAllText(Path.Combine(logsDirectory, "all.log"), line.LineData + "\n"); + + 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"; + + File.AppendAllText(Path.Combine(logsDirectory, line.LoggerLevel.Name + ".log"), contentFile); + File.AppendAllText(Path.Combine(logsDirectory, "all.log"), contentAll); } } } \ No newline at end of file diff --git a/ProjectLighthouse/Logging/LoggerLevels.cs b/ProjectLighthouse/Logging/LoggerLevels.cs index 4f8de4e3..b033f58b 100644 --- a/ProjectLighthouse/Logging/LoggerLevels.cs +++ b/ProjectLighthouse/Logging/LoggerLevels.cs @@ -1,55 +1,39 @@ using Kettu; +using Microsoft.Extensions.Logging; -namespace LBPUnion.ProjectLighthouse.Logging { - public class LoggerLevelStartup : LoggerLevel { - public override string Name => "Startup"; +namespace LBPUnion.ProjectLighthouse.Logging +{ + public class LoggerLevelStartup : LoggerLevel + { public static readonly LoggerLevelStartup Instance = new(); + public override string Name => "Startup"; } - public class LoggerLevelDatabase : LoggerLevel { - public override string Name => "Database"; + public class LoggerLevelDatabase : LoggerLevel + { public static readonly LoggerLevelDatabase Instance = new(); + public override string Name => "Database"; } - public class LoggerLevelHttp : LoggerLevel { - public override string Name => "HTTP"; + public class LoggerLevelHttp : LoggerLevel + { public static readonly LoggerLevelHttp Instance = new(); + public override string Name => "HTTP"; } - #region ASP.NET - public class LoggerLevelAspNetTrace : LoggerLevel { - public override string Name => "ASP.NET: Trace"; - public static readonly LoggerLevelAspNetTrace Instance = new(); + public class LoggerLevelFilter : LoggerLevel + { + public static readonly LoggerLevelFilter Instance = new(); + public override string Name => "Filter"; } - - public class LoggerLevelAspNetDebug : LoggerLevel { - public override string Name => "ASP.NET: Debug"; - public static readonly LoggerLevelAspNetDebug Instance = new(); + + public class LoggerLevelAspNet : LoggerLevel + { + + public LoggerLevelAspNet(LogLevel level) + { + this.Channel = level.ToString(); + } + public override string Name => "AspNet"; } - - public class LoggerLevelAspNetInformation : LoggerLevel { - public override string Name => "ASP.NET: Information"; - public static readonly LoggerLevelAspNetInformation Instance = new(); - } - - public class LoggerLevelAspNetWarning : LoggerLevel { - public override string Name => "ASP.NET: Warning"; - public static readonly LoggerLevelAspNetWarning Instance = new(); - } - - public class LoggerLevelAspNetError : LoggerLevel { - public override string Name => "ASP.NET: Error"; - public static readonly LoggerLevelAspNetError Instance = new(); - } - - public class LoggerLevelAspNetCritical : LoggerLevel { - public override string Name => "ASP.NET: Critical"; - public static readonly LoggerLevelAspNetCritical Instance = new(); - } - - public class LoggerLevelAspNetNone : LoggerLevel { - public override string Name => "ASP.NET: None"; - public static readonly LoggerLevelAspNetNone Instance = new(); - } - #endregion } \ No newline at end of file diff --git a/ProjectLighthouse/Logging/NullScope.cs b/ProjectLighthouse/Logging/NullScope.cs index 3152fea5..5d790f78 100644 --- a/ProjectLighthouse/Logging/NullScope.cs +++ b/ProjectLighthouse/Logging/NullScope.cs @@ -1,12 +1,16 @@ using System; -namespace LBPUnion.ProjectLighthouse.Logging { - public class NullScope : IDisposable{ +namespace LBPUnion.ProjectLighthouse.Logging +{ + public class NullScope : IDisposable + { + + private NullScope() + {} public static NullScope Instance { get; } = new(); - private NullScope() {} - - public void Dispose() { + public void Dispose() + { GC.SuppressFinalize(this); } } diff --git a/ProjectLighthouse/Migrations/20211028015915_AddSlotTimestamp.Designer.cs b/ProjectLighthouse/Migrations/20211028015915_AddSlotTimestamp.Designer.cs new file mode 100644 index 00000000..48beed7d --- /dev/null +++ b/ProjectLighthouse/Migrations/20211028015915_AddSlotTimestamp.Designer.cs @@ -0,0 +1,421 @@ +// +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211028015915_AddSlotTimestamp")] + partial class AddSlotTimestamp + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 64) + .HasAnnotation("ProductVersion", "5.0.11"); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.Property("HeartedProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("HeartedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedProfileId"); + + b.HasIndex("HeartedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedProfiles"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.Property("HeartedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.Property("QueuedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("QueuedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("QueuedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.Property("SlotId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuthorLabels") + .HasColumnType("longtext"); + + b.Property("BackgroundHash") + .HasColumnType("longtext"); + + b.Property("CreatorId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("InitiallyLocked") + .HasColumnType("tinyint(1)"); + + b.Property("Lbp1Only") + .HasColumnType("tinyint(1)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MaximumPlayers") + .HasColumnType("int"); + + b.Property("MinimumPlayers") + .HasColumnType("int"); + + b.Property("MoveRequired") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("ResourceCollection") + .HasColumnType("longtext"); + + b.Property("RootLevel") + .HasColumnType("longtext"); + + b.Property("Shareable") + .HasColumnType("int"); + + b.Property("SubLevel") + .HasColumnType("tinyint(1)"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("SlotId"); + + b.HasIndex("CreatorId"); + + b.HasIndex("LocationId"); + + b.ToTable("Slots"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("PosterUserId") + .HasColumnType("int"); + + b.Property("TargetUserId") + .HasColumnType("int"); + + b.Property("ThumbsDown") + .HasColumnType("int"); + + b.Property("ThumbsUp") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("CommentId"); + + b.HasIndex("PosterUserId"); + + b.HasIndex("TargetUserId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastMatch", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("UserId"); + + b.ToTable("LastMatches"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("X") + .HasColumnType("int"); + + b.Property("Y") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Token", b => + { + b.Property("TokenId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserToken") + .HasColumnType("longtext"); + + b.HasKey("TokenId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Biography") + .HasColumnType("longtext"); + + b.Property("BooHash") + .HasColumnType("longtext"); + + b.Property("CommentCount") + .HasColumnType("int"); + + b.Property("CommentsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("FavouriteSlotCount") + .HasColumnType("int"); + + b.Property("FavouriteUserCount") + .HasColumnType("int"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("HeartCount") + .HasColumnType("int"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("Lists") + .HasColumnType("int"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("LolCatFtwCount") + .HasColumnType("int"); + + b.Property("PhotosByMeCount") + .HasColumnType("int"); + + b.Property("PhotosWithMeCount") + .HasColumnType("int"); + + b.Property("Pins") + .HasColumnType("longtext"); + + b.Property("PlanetHash") + .HasColumnType("longtext"); + + b.Property("ReviewCount") + .HasColumnType("int"); + + b.Property("StaffChallengeBronzeCount") + .HasColumnType("int"); + + b.Property("StaffChallengeGoldCount") + .HasColumnType("int"); + + b.Property("StaffChallengeSilverCount") + .HasColumnType("int"); + + b.Property("UsedSlots") + .HasColumnType("int"); + + b.Property("Username") + .HasColumnType("longtext"); + + b.Property("YayHash") + .HasColumnType("longtext"); + + b.HasKey("UserId"); + + b.HasIndex("LocationId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser") + .WithMany() + .HasForeignKey("HeartedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HeartedUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster") + .WithMany() + .HasForeignKey("PosterUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poster"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ProjectLighthouse/Migrations/20211028015915_AddSlotTimestamp.cs b/ProjectLighthouse/Migrations/20211028015915_AddSlotTimestamp.cs new file mode 100644 index 00000000..9ff79d40 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211028015915_AddSlotTimestamp.cs @@ -0,0 +1,25 @@ +using LBPUnion.ProjectLighthouse.Helpers; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ProjectLighthouse.Migrations +{ + public partial class AddSlotTimestamp : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Timestamp", + table: "Slots", + type: "bigint", + nullable: false, + defaultValue: TimeHelper.UnixTimeMilliseconds()); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Timestamp", + table: "Slots"); + } + } +} diff --git a/ProjectLighthouse/Migrations/20211028021513_AddSlotFirstUploadedAndLastUpdated.Designer.cs b/ProjectLighthouse/Migrations/20211028021513_AddSlotFirstUploadedAndLastUpdated.Designer.cs new file mode 100644 index 00000000..d1ba6622 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211028021513_AddSlotFirstUploadedAndLastUpdated.Designer.cs @@ -0,0 +1,424 @@ +// +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211028021513_AddSlotFirstUploadedAndLastUpdated")] + partial class AddSlotFirstUploadedAndLastUpdated + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 64) + .HasAnnotation("ProductVersion", "5.0.11"); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.Property("HeartedProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("HeartedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedProfileId"); + + b.HasIndex("HeartedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedProfiles"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.Property("HeartedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.Property("QueuedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("QueuedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("QueuedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.Property("SlotId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuthorLabels") + .HasColumnType("longtext"); + + b.Property("BackgroundHash") + .HasColumnType("longtext"); + + b.Property("CreatorId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("FirstUploaded") + .HasColumnType("bigint"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("InitiallyLocked") + .HasColumnType("tinyint(1)"); + + b.Property("LastUpdated") + .HasColumnType("bigint"); + + b.Property("Lbp1Only") + .HasColumnType("tinyint(1)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MaximumPlayers") + .HasColumnType("int"); + + b.Property("MinimumPlayers") + .HasColumnType("int"); + + b.Property("MoveRequired") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("ResourceCollection") + .HasColumnType("longtext"); + + b.Property("RootLevel") + .HasColumnType("longtext"); + + b.Property("Shareable") + .HasColumnType("int"); + + b.Property("SubLevel") + .HasColumnType("tinyint(1)"); + + b.HasKey("SlotId"); + + b.HasIndex("CreatorId"); + + b.HasIndex("LocationId"); + + b.ToTable("Slots"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("PosterUserId") + .HasColumnType("int"); + + b.Property("TargetUserId") + .HasColumnType("int"); + + b.Property("ThumbsDown") + .HasColumnType("int"); + + b.Property("ThumbsUp") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("CommentId"); + + b.HasIndex("PosterUserId"); + + b.HasIndex("TargetUserId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastMatch", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("UserId"); + + b.ToTable("LastMatches"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("X") + .HasColumnType("int"); + + b.Property("Y") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Token", b => + { + b.Property("TokenId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserToken") + .HasColumnType("longtext"); + + b.HasKey("TokenId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Biography") + .HasColumnType("longtext"); + + b.Property("BooHash") + .HasColumnType("longtext"); + + b.Property("CommentCount") + .HasColumnType("int"); + + b.Property("CommentsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("FavouriteSlotCount") + .HasColumnType("int"); + + b.Property("FavouriteUserCount") + .HasColumnType("int"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("HeartCount") + .HasColumnType("int"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("Lists") + .HasColumnType("int"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("LolCatFtwCount") + .HasColumnType("int"); + + b.Property("PhotosByMeCount") + .HasColumnType("int"); + + b.Property("PhotosWithMeCount") + .HasColumnType("int"); + + b.Property("Pins") + .HasColumnType("longtext"); + + b.Property("PlanetHash") + .HasColumnType("longtext"); + + b.Property("ReviewCount") + .HasColumnType("int"); + + b.Property("StaffChallengeBronzeCount") + .HasColumnType("int"); + + b.Property("StaffChallengeGoldCount") + .HasColumnType("int"); + + b.Property("StaffChallengeSilverCount") + .HasColumnType("int"); + + b.Property("UsedSlots") + .HasColumnType("int"); + + b.Property("Username") + .HasColumnType("longtext"); + + b.Property("YayHash") + .HasColumnType("longtext"); + + b.HasKey("UserId"); + + b.HasIndex("LocationId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser") + .WithMany() + .HasForeignKey("HeartedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HeartedUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster") + .WithMany() + .HasForeignKey("PosterUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poster"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ProjectLighthouse/Migrations/20211028021513_AddSlotFirstUploadedAndLastUpdated.cs b/ProjectLighthouse/Migrations/20211028021513_AddSlotFirstUploadedAndLastUpdated.cs new file mode 100644 index 00000000..58564418 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211028021513_AddSlotFirstUploadedAndLastUpdated.cs @@ -0,0 +1,35 @@ +using LBPUnion.ProjectLighthouse.Helpers; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ProjectLighthouse.Migrations +{ + public partial class AddSlotFirstUploadedAndLastUpdated : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Timestamp", + table: "Slots", + newName: "LastUpdated"); + + migrationBuilder.AddColumn( + name: "FirstUploaded", + table: "Slots", + type: "bigint", + nullable: false, + defaultValue: TimeHelper.UnixTimeMilliseconds()); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "FirstUploaded", + table: "Slots"); + + migrationBuilder.RenameColumn( + name: "LastUpdated", + table: "Slots", + newName: "Timestamp"); + } + } +} diff --git a/ProjectLighthouse/Migrations/20211029213334_RemoveUsedSlotsFromDb.Designer.cs b/ProjectLighthouse/Migrations/20211029213334_RemoveUsedSlotsFromDb.Designer.cs new file mode 100644 index 00000000..59a0dcc7 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211029213334_RemoveUsedSlotsFromDb.Designer.cs @@ -0,0 +1,421 @@ +// +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211029213334_RemoveUsedSlotsFromDb")] + partial class RemoveUsedSlotsFromDb + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 64) + .HasAnnotation("ProductVersion", "5.0.11"); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.Property("HeartedProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("HeartedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedProfileId"); + + b.HasIndex("HeartedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedProfiles"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.Property("HeartedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.Property("QueuedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("QueuedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("QueuedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.Property("SlotId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuthorLabels") + .HasColumnType("longtext"); + + b.Property("BackgroundHash") + .HasColumnType("longtext"); + + b.Property("CreatorId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("FirstUploaded") + .HasColumnType("bigint"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("InitiallyLocked") + .HasColumnType("tinyint(1)"); + + b.Property("LastUpdated") + .HasColumnType("bigint"); + + b.Property("Lbp1Only") + .HasColumnType("tinyint(1)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MaximumPlayers") + .HasColumnType("int"); + + b.Property("MinimumPlayers") + .HasColumnType("int"); + + b.Property("MoveRequired") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("ResourceCollection") + .HasColumnType("longtext"); + + b.Property("RootLevel") + .HasColumnType("longtext"); + + b.Property("Shareable") + .HasColumnType("int"); + + b.Property("SubLevel") + .HasColumnType("tinyint(1)"); + + b.HasKey("SlotId"); + + b.HasIndex("CreatorId"); + + b.HasIndex("LocationId"); + + b.ToTable("Slots"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("PosterUserId") + .HasColumnType("int"); + + b.Property("TargetUserId") + .HasColumnType("int"); + + b.Property("ThumbsDown") + .HasColumnType("int"); + + b.Property("ThumbsUp") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("CommentId"); + + b.HasIndex("PosterUserId"); + + b.HasIndex("TargetUserId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastMatch", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("UserId"); + + b.ToTable("LastMatches"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("X") + .HasColumnType("int"); + + b.Property("Y") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Token", b => + { + b.Property("TokenId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserToken") + .HasColumnType("longtext"); + + b.HasKey("TokenId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Biography") + .HasColumnType("longtext"); + + b.Property("BooHash") + .HasColumnType("longtext"); + + b.Property("CommentCount") + .HasColumnType("int"); + + b.Property("CommentsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("FavouriteSlotCount") + .HasColumnType("int"); + + b.Property("FavouriteUserCount") + .HasColumnType("int"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("HeartCount") + .HasColumnType("int"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("Lists") + .HasColumnType("int"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("LolCatFtwCount") + .HasColumnType("int"); + + b.Property("PhotosByMeCount") + .HasColumnType("int"); + + b.Property("PhotosWithMeCount") + .HasColumnType("int"); + + b.Property("Pins") + .HasColumnType("longtext"); + + b.Property("PlanetHash") + .HasColumnType("longtext"); + + b.Property("ReviewCount") + .HasColumnType("int"); + + b.Property("StaffChallengeBronzeCount") + .HasColumnType("int"); + + b.Property("StaffChallengeGoldCount") + .HasColumnType("int"); + + b.Property("StaffChallengeSilverCount") + .HasColumnType("int"); + + b.Property("Username") + .HasColumnType("longtext"); + + b.Property("YayHash") + .HasColumnType("longtext"); + + b.HasKey("UserId"); + + b.HasIndex("LocationId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser") + .WithMany() + .HasForeignKey("HeartedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HeartedUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster") + .WithMany() + .HasForeignKey("PosterUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poster"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ProjectLighthouse/Migrations/20211029213334_RemoveUsedSlotsFromDb.cs b/ProjectLighthouse/Migrations/20211029213334_RemoveUsedSlotsFromDb.cs new file mode 100644 index 00000000..7656c6ad --- /dev/null +++ b/ProjectLighthouse/Migrations/20211029213334_RemoveUsedSlotsFromDb.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ProjectLighthouse.Migrations +{ + public partial class RemoveUsedSlotsFromDb : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UsedSlots", + table: "Users"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UsedSlots", + table: "Users", + type: "int", + nullable: false, + defaultValue: 0); + } + } +} diff --git a/ProjectLighthouse/Migrations/20211030203837_AddMMPickToSlot.Designer.cs b/ProjectLighthouse/Migrations/20211030203837_AddMMPickToSlot.Designer.cs new file mode 100644 index 00000000..f9f84485 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211030203837_AddMMPickToSlot.Designer.cs @@ -0,0 +1,424 @@ +// +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20211030203837_AddMMPickToSlot")] + partial class AddMMPickToSlot + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 64) + .HasAnnotation("ProductVersion", "5.0.11"); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.Property("HeartedProfileId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("HeartedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedProfileId"); + + b.HasIndex("HeartedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedProfiles"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.Property("HeartedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("HeartedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("HeartedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.Property("QueuedLevelId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("SlotId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("QueuedLevelId"); + + b.HasIndex("SlotId"); + + b.HasIndex("UserId"); + + b.ToTable("QueuedLevels"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.Property("SlotId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AuthorLabels") + .HasColumnType("longtext"); + + b.Property("BackgroundHash") + .HasColumnType("longtext"); + + b.Property("CreatorId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("FirstUploaded") + .HasColumnType("bigint"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("InitiallyLocked") + .HasColumnType("tinyint(1)"); + + b.Property("LastUpdated") + .HasColumnType("bigint"); + + b.Property("Lbp1Only") + .HasColumnType("tinyint(1)"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("MMPick") + .HasColumnType("tinyint(1)"); + + b.Property("MaximumPlayers") + .HasColumnType("int"); + + b.Property("MinimumPlayers") + .HasColumnType("int"); + + b.Property("MoveRequired") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("ResourceCollection") + .HasColumnType("longtext"); + + b.Property("RootLevel") + .HasColumnType("longtext"); + + b.Property("Shareable") + .HasColumnType("int"); + + b.Property("SubLevel") + .HasColumnType("tinyint(1)"); + + b.HasKey("SlotId"); + + b.HasIndex("CreatorId"); + + b.HasIndex("LocationId"); + + b.ToTable("Slots"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.Property("CommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .HasColumnType("longtext"); + + b.Property("PosterUserId") + .HasColumnType("int"); + + b.Property("TargetUserId") + .HasColumnType("int"); + + b.Property("ThumbsDown") + .HasColumnType("int"); + + b.Property("ThumbsUp") + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("CommentId"); + + b.HasIndex("PosterUserId"); + + b.HasIndex("TargetUserId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastMatch", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Timestamp") + .HasColumnType("bigint"); + + b.HasKey("UserId"); + + b.ToTable("LastMatches"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("X") + .HasColumnType("int"); + + b.Property("Y") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Token", b => + { + b.Property("TokenId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("UserToken") + .HasColumnType("longtext"); + + b.HasKey("TokenId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Biography") + .HasColumnType("longtext"); + + b.Property("BooHash") + .HasColumnType("longtext"); + + b.Property("CommentCount") + .HasColumnType("int"); + + b.Property("CommentsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("FavouriteSlotCount") + .HasColumnType("int"); + + b.Property("FavouriteUserCount") + .HasColumnType("int"); + + b.Property("Game") + .HasColumnType("int"); + + b.Property("HeartCount") + .HasColumnType("int"); + + b.Property("IconHash") + .HasColumnType("longtext"); + + b.Property("Lists") + .HasColumnType("int"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("LolCatFtwCount") + .HasColumnType("int"); + + b.Property("PhotosByMeCount") + .HasColumnType("int"); + + b.Property("PhotosWithMeCount") + .HasColumnType("int"); + + b.Property("Pins") + .HasColumnType("longtext"); + + b.Property("PlanetHash") + .HasColumnType("longtext"); + + b.Property("ReviewCount") + .HasColumnType("int"); + + b.Property("StaffChallengeBronzeCount") + .HasColumnType("int"); + + b.Property("StaffChallengeGoldCount") + .HasColumnType("int"); + + b.Property("StaffChallengeSilverCount") + .HasColumnType("int"); + + b.Property("Username") + .HasColumnType("longtext"); + + b.Property("YayHash") + .HasColumnType("longtext"); + + b.HasKey("UserId"); + + b.HasIndex("LocationId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser") + .WithMany() + .HasForeignKey("HeartedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HeartedUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Slot"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Creator"); + + b.Navigation("Location"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster") + .WithMany() + .HasForeignKey("PosterUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Target") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poster"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") + .WithMany() + .HasForeignKey("LocationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ProjectLighthouse/Migrations/20211030203837_AddMMPickToSlot.cs b/ProjectLighthouse/Migrations/20211030203837_AddMMPickToSlot.cs new file mode 100644 index 00000000..605b9904 --- /dev/null +++ b/ProjectLighthouse/Migrations/20211030203837_AddMMPickToSlot.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ProjectLighthouse.Migrations +{ + public partial class AddMMPickToSlot : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MMPick", + table: "Slots", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MMPick", + table: "Slots"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 84ad0e5c..4fbf4e28 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -97,18 +97,27 @@ namespace ProjectLighthouse.Migrations b.Property("Description") .HasColumnType("longtext"); + b.Property("FirstUploaded") + .HasColumnType("bigint"); + b.Property("IconHash") .HasColumnType("longtext"); b.Property("InitiallyLocked") .HasColumnType("tinyint(1)"); + b.Property("LastUpdated") + .HasColumnType("bigint"); + b.Property("Lbp1Only") .HasColumnType("tinyint(1)"); b.Property("LocationId") .HasColumnType("int"); + b.Property("MMPick") + .HasColumnType("tinyint(1)"); + b.Property("MaximumPlayers") .HasColumnType("int"); @@ -289,9 +298,6 @@ namespace ProjectLighthouse.Migrations b.Property("StaffChallengeSilverCount") .HasColumnType("int"); - b.Property("UsedSlots") - .HasColumnType("int"); - b.Property("Username") .HasColumnType("longtext"); diff --git a/ProjectLighthouse/Program.cs b/ProjectLighthouse/Program.cs index c9a49d9f..804f9c94 100644 --- a/ProjectLighthouse/Program.cs +++ b/ProjectLighthouse/Program.cs @@ -1,7 +1,11 @@ using System; using System.Diagnostics; +using System.Linq; using Kettu; +using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Logging; +using LBPUnion.ProjectLighthouse.Types.Levels; +using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; @@ -17,8 +21,8 @@ namespace LBPUnion.ProjectLighthouse public static void Main(string[] args) { // Log startup time - Stopwatch startupStopwatch = new(); - startupStopwatch.Start(); + Stopwatch stopwatch = new(); + stopwatch.Start(); // Setup logging @@ -30,39 +34,67 @@ namespace LBPUnion.ProjectLighthouse Logger.Log("Welcome to Project Lighthouse!", LoggerLevelStartup.Instance); Logger.Log("Determining if the database is available...", LoggerLevelStartup.Instance); bool dbConnected = ServerSettings.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) - { - Stopwatch migrationStopwatch = new(); - migrationStopwatch.Start(); + if (!dbConnected) Environment.Exit(1); + using Database database = new(); - Logger.Log("Migrating database...", LoggerLevelDatabase.Instance); - using Database database = new(); - database.Database.Migrate(); + Logger.Log("Migrating database...", LoggerLevelDatabase.Instance); + MigrateDatabase(database); - migrationStopwatch.Stop(); - Logger.Log($"Migration took {migrationStopwatch.ElapsedMilliseconds}ms.", LoggerLevelDatabase.Instance); - } - else Environment.Exit(1); + Logger.Log("Fixing broken timestamps...", LoggerLevelDatabase.Instance); + FixTimestamps(database); - startupStopwatch.Stop(); - Logger.Log( - $"Ready! Startup took {startupStopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", - LoggerLevelStartup.Instance); + stopwatch.Stop(); + Logger.Log($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LoggerLevelStartup.Instance); CreateHostBuilder(args).Build().Run(); } - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }) - .ConfigureLogging(logging => - { - logging.ClearProviders(); - logging.Services.TryAddEnumerable(ServiceDescriptor - .Singleton()); - }); + public static void MigrateDatabase(Database database) + { + Stopwatch stopwatch = new(); + stopwatch.Start(); + + database.Database.Migrate(); + + stopwatch.Stop(); + Logger.Log($"Migration took {stopwatch.ElapsedMilliseconds}ms.", LoggerLevelDatabase.Instance); + } + + public static void FixTimestamps(Database database) + { + Stopwatch stopwatch = new(); + stopwatch.Start(); + + foreach (Slot slot in database.Slots.Where(s => s.FirstUploaded == 0)) slot.FirstUploaded = TimeHelper.UnixTimeMilliseconds(); + + foreach (Slot slot in database.Slots.Where(s => s.LastUpdated == 0)) slot.LastUpdated = TimeHelper.UnixTimeMilliseconds(); + + foreach (Comment comment in database.Comments.Where(c => c.Timestamp == 0)) comment.Timestamp = TimeHelper.UnixTimeMilliseconds(); + + database.SaveChanges(); + + stopwatch.Stop(); + Logger.Log($"Fixing timestamps took {stopwatch.ElapsedMilliseconds}ms.", LoggerLevelDatabase.Instance); + } + + public static IHostBuilder CreateHostBuilder(string[] args) + => Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults + ( + webBuilder => + { + webBuilder.UseStartup(); + } + ) + .ConfigureLogging + ( + logging => + { + logging.ClearProviders(); + logging.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); + } + ); } } \ No newline at end of file diff --git a/ProjectLighthouse/ProjectLighthouse.csproj b/ProjectLighthouse/ProjectLighthouse.csproj index b7a138f8..3a7ade23 100644 --- a/ProjectLighthouse/ProjectLighthouse.csproj +++ b/ProjectLighthouse/ProjectLighthouse.csproj @@ -8,19 +8,23 @@ - - - - + + + + - all - runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive - + - + + + + + diff --git a/ProjectLighthouse/Properties/launchSettings.json b/ProjectLighthouse/Properties/launchSettings.json index a8f5a37b..5b4d172c 100644 --- a/ProjectLighthouse/Properties/launchSettings.json +++ b/ProjectLighthouse/Properties/launchSettings.json @@ -18,7 +18,7 @@ "ProjectLighthouse": { "commandName": "Project", "dotnetRunMessages": "true", - "applicationUrl": "http://localhost:10060;http://localhost:10061;http://localhost:1062", + "applicationUrl": "http://localhost:10060", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "LIGHTHOUSE_DB_CONNECTION_STRING": "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse" diff --git a/ProjectLighthouse/Serialization/LbpSerializer.cs b/ProjectLighthouse/Serialization/LbpSerializer.cs index e7946c02..1d2cfdaf 100644 --- a/ProjectLighthouse/Serialization/LbpSerializer.cs +++ b/ProjectLighthouse/Serialization/LbpSerializer.cs @@ -2,26 +2,30 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -namespace LBPUnion.ProjectLighthouse.Serialization { +namespace LBPUnion.ProjectLighthouse.Serialization +{ /// - /// 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. + /// 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. /// [SuppressMessage("ReSharper", "UnusedMember.Global")] - public static class LbpSerializer { + public static class LbpSerializer + { public static string BlankElement(string key) => $"<{key}>"; public static string StringElement(KeyValuePair pair) => $"<{pair.Key}>{pair.Value}"; public static string StringElement(string key, object value) => $"<{key}>{value}"; - - public static string TaggedStringElement(KeyValuePair pair, KeyValuePair tagPair) => - $"<{pair.Key} {tagPair.Key}=\"{tagPair.Value}\">{pair.Value}"; - - public static string TaggedStringElement(string key, object value, string tagKey, object tagValue) => - $"<{key} {tagKey}=\"{tagValue}\">{value}"; - public static string Elements(params KeyValuePair[] pairs) => - pairs.Aggregate(string.Empty, (current, pair) => current + StringElement(pair)); + public static string TaggedStringElement + (KeyValuePair pair, KeyValuePair tagPair) + => $"<{pair.Key} {tagPair.Key}=\"{tagPair.Value}\">{pair.Value}"; + + public static string TaggedStringElement(string key, object value, string tagKey, object tagValue) => $"<{key} {tagKey}=\"{tagValue}\">{value}"; + + public static string Elements + (params KeyValuePair[] pairs) + => pairs.Aggregate(string.Empty, (current, pair) => current + StringElement(pair)); } } \ No newline at end of file diff --git a/ProjectLighthouse/Serialization/XmlOutputFormatter.cs b/ProjectLighthouse/Serialization/XmlOutputFormatter.cs index da7c25ae..6103f8cd 100644 --- a/ProjectLighthouse/Serialization/XmlOutputFormatter.cs +++ b/ProjectLighthouse/Serialization/XmlOutputFormatter.cs @@ -1,8 +1,11 @@ using Microsoft.AspNetCore.Mvc.Formatters; -namespace LBPUnion.ProjectLighthouse.Serialization { - public class XmlOutputFormatter : StringOutputFormatter { - public XmlOutputFormatter() { +namespace LBPUnion.ProjectLighthouse.Serialization +{ + public class XmlOutputFormatter : StringOutputFormatter + { + public XmlOutputFormatter() + { this.SupportedMediaTypes.Add("text/xml"); this.SupportedMediaTypes.Add("application/xml"); } diff --git a/ProjectLighthouse/Startup.cs b/ProjectLighthouse/Startup.cs index ebd94c55..6b6775df 100644 --- a/ProjectLighthouse/Startup.cs +++ b/ProjectLighthouse/Startup.cs @@ -1,9 +1,7 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Reflection.Metadata.Ecma335; -using System.Runtime.InteropServices.ComTypes; -using System.Threading.Tasks; using Kettu; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Logging; @@ -14,7 +12,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; namespace LBPUnion.ProjectLighthouse { @@ -31,8 +29,8 @@ namespace LBPUnion.ProjectLighthouse public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - services.AddMvc(options => - options.OutputFormatters.Add(new XmlOutputFormatter())); + + services.AddMvc(options => options.OutputFormatters.Add(new XmlOutputFormatter())); services.AddDbContext(); } @@ -44,100 +42,96 @@ namespace LBPUnion.ProjectLighthouse string serverDigestKey = Environment.GetEnvironmentVariable("SERVER_DIGEST_KEY"); if (string.IsNullOrWhiteSpace(serverDigestKey)) { - Logger.Log( + Logger.Log + ( "The SERVER_DIGEST_KEY environment variable wasn't set, so digest headers won't be set or verified. This will prevent LBP 1 and LBP 3 from working. " + "To increase security, it is recommended that you find and set this variable." ); computeDigests = false; } - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } + + if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); // Logs every request and the response to it // Example: "200, 13ms: GET /LITTLEBIGPLANETPS3_XML/news" // Example: "404, 127ms: GET /asdasd?query=osucookiezi727ppbluezenithtopplayhdhr" - app.Use(async (context, next) => - { - Stopwatch requestStopwatch = new(); - requestStopwatch.Start(); - - // Log all headers. - foreach (var header in context.Request.Headers) - Logger.Log($"{header.Key}: {header.Value}"); - - context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging - - // Client digest check. - string authCookie; - if (!context.Request.Cookies.TryGetValue("MM_AUTH", out authCookie)) - authCookie = string.Empty; - string digestPath = context.Request.Path; - Stream body = context.Request.Body; - - if (computeDigests) + app.Use + ( + async (context, next) => { - string clientRequestDigest = - await HashHelper.ComputeDigest(digestPath, authCookie, body, serverDigestKey); + Stopwatch requestStopwatch = new(); + requestStopwatch.Start(); - // Check the digest we've just calculated against the X-Digest-A header if the game set the header. They should match. - if (context.Request.Headers.TryGetValue("X-Digest-A", out var sentDigest)) + // Log all headers. + foreach (KeyValuePair header in context.Request.Headers) Logger.Log($"{header.Key}: {header.Value}"); + + context.Request.EnableBuffering(); // Allows us to reset the position of Request.Body for later logging + + // Client digest check. + string authCookie; + if (!context.Request.Cookies.TryGetValue("MM_AUTH", out authCookie)) authCookie = string.Empty; + string digestPath = context.Request.Path; + Stream body = context.Request.Body; + + if (computeDigests) { - if (clientRequestDigest != sentDigest) - { - context.Response.StatusCode = 403; - context.Abort(); - return; - } + string clientRequestDigest = await HashHelper.ComputeDigest(digestPath, authCookie, body, serverDigestKey); + + // Check the digest we've just calculated against the X-Digest-A header if the game set the header. They should match. + if (context.Request.Headers.TryGetValue("X-Digest-A", out StringValues sentDigest)) + if (clientRequestDigest != sentDigest) + { + context.Response.StatusCode = 403; + context.Abort(); + return; + } + + context.Response.Headers.Add("X-Digest-B", clientRequestDigest); + context.Request.Body.Position = 0; } - context.Response.Headers.Add("X-Digest-B", clientRequestDigest); - context.Request.Body.Position = 0; - } + // This does the same as above, but for the response stream. + using MemoryStream responseBuffer = new(); + Stream oldResponseStream = context.Response.Body; + context.Response.Body = responseBuffer; - // This does the same as above, but for the response stream. - using MemoryStream responseBuffer = new MemoryStream(); - Stream oldResponseStream = context.Response.Body; - context.Response.Body = responseBuffer; + await next(); // Handle the request so we can get the status code from it - await next(); // Handle the request so we can get the status code from it + // Compute the server digest hash. + if (computeDigests) + { + responseBuffer.Position = 0; - // Compute the server digest hash. - if (computeDigests) - { + // Compute the digest for the response. + string serverDigest = await HashHelper.ComputeDigest(context.Request.Path, authCookie, responseBuffer, serverDigestKey); + context.Response.Headers.Add("X-Digest-A", serverDigest); + } + + // Set the X-Original-Content-Length header to the length of the response buffer. + context.Response.Headers.Add("X-Original-Content-Length", responseBuffer.Length.ToString()); + + // Copy the buffered response to the actual respose stream. responseBuffer.Position = 0; - - // Compute the digest for the response. - string serverDigest = await HashHelper.ComputeDigest(context.Request.Path, authCookie, - responseBuffer, serverDigestKey); - context.Response.Headers.Add("X-Digest-A", serverDigest); + + await responseBuffer.CopyToAsync(oldResponseStream); + + context.Response.Body = oldResponseStream; + + requestStopwatch.Stop(); + + Logger.Log + ( + $"{context.Response.StatusCode}, {requestStopwatch.ElapsedMilliseconds}ms: {context.Request.Method} {context.Request.Path}{context.Request.QueryString}", + LoggerLevelHttp.Instance + ); + + if (context.Request.Method == "POST") + { + context.Request.Body.Position = 0; + Logger.Log(await new StreamReader(context.Request.Body).ReadToEndAsync(), LoggerLevelHttp.Instance); + } } - - // Set the X-Original-Content-Length header to the length of the response buffer. - context.Response.Headers.Add("X-Original-Content-Length", responseBuffer.Length.ToString()); - - // Copy the buffered response to the actual respose stream. - responseBuffer.Position = 0; - - await responseBuffer.CopyToAsync(oldResponseStream); - - context.Response.Body = oldResponseStream; - - requestStopwatch.Stop(); - - Logger.Log( - $"{context.Response.StatusCode}, {requestStopwatch.ElapsedMilliseconds}ms: {context.Request.Method} {context.Request.Path}{context.Request.QueryString}", - LoggerLevelHttp.Instance - ); - - if (context.Request.Method == "POST") - { - context.Request.Body.Position = 0; - Logger.Log(await new StreamReader(context.Request.Body).ReadToEndAsync(), LoggerLevelHttp.Instance); - } - }); + ); app.UseRouting(); diff --git a/ProjectLighthouse/Types/Files/LbpFile.cs b/ProjectLighthouse/Types/Files/LbpFile.cs index 70ad51da..b226276c 100644 --- a/ProjectLighthouse/Types/Files/LbpFile.cs +++ b/ProjectLighthouse/Types/Files/LbpFile.cs @@ -1,21 +1,24 @@ using LBPUnion.ProjectLighthouse.Helpers; -namespace LBPUnion.ProjectLighthouse.Types.Files { - public class LbpFile { - public LbpFile(byte[] data) { - this.Data = data; - this.FileType = FileHelper.DetermineFileType(this.Data); - } - - /// - /// The type of file. - /// - public LbpFileType FileType; +namespace LBPUnion.ProjectLighthouse.Types.Files +{ + public class LbpFile + { /// - /// A buffer of the file's data. + /// A buffer of the file's data. /// public readonly byte[] Data; + /// + /// The type of file. + /// + public LbpFileType FileType; + + public LbpFile(byte[] data) + { + this.Data = data; + this.FileType = FileHelper.DetermineFileType(this.Data); + } } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Files/LbpFileType.cs b/ProjectLighthouse/Types/Files/LbpFileType.cs index fab63a15..871aac91 100644 --- a/ProjectLighthouse/Types/Files/LbpFileType.cs +++ b/ProjectLighthouse/Types/Files/LbpFileType.cs @@ -1,11 +1,14 @@ -namespace LBPUnion.ProjectLighthouse.Types.Files { - public enum LbpFileType { +namespace LBPUnion.ProjectLighthouse.Types.Files +{ + public enum LbpFileType + { Script, // .ff, FSH Texture, // TEX Level, // LVL FileArchive, // .farc, (ends with FARC) Plan, // PLN, uploaded with levels Voice, // VOP, voice data + Painting, // PTG, paintings Unknown, } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/HeartedProfile.cs b/ProjectLighthouse/Types/HeartedProfile.cs index 6d2908f0..bf4ebea5 100644 --- a/ProjectLighthouse/Types/HeartedProfile.cs +++ b/ProjectLighthouse/Types/HeartedProfile.cs @@ -2,22 +2,27 @@ using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace LBPUnion.ProjectLighthouse.Types { - public class HeartedProfile { +namespace LBPUnion.ProjectLighthouse.Types +{ + public class HeartedProfile + { // ReSharper disable once UnusedMember.Global #if NET6_0_OR_GREATER [Obsolete($"Use {nameof(HeartedUserId)} instead, this is a key which you should never need to use.")] #else [Obsolete("Use HeartedUserId instead, this is a key which you should never need to use.")] #endif - [Key] public int HeartedProfileId { get; set; } + [Key] + public int HeartedProfileId { get; set; } public int UserId { get; set; } - [ForeignKey(nameof(UserId))] public User User { get; set; } + [ForeignKey(nameof(UserId))] + public User User { get; set; } public int HeartedUserId { get; set; } - [ForeignKey(nameof(HeartedUserId))] public User HeartedUser { get; set; } + [ForeignKey(nameof(HeartedUserId))] + public User HeartedUser { get; set; } } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Levels/HeartedLevel.cs b/ProjectLighthouse/Types/Levels/HeartedLevel.cs index 40c55dea..72b9855b 100644 --- a/ProjectLighthouse/Types/Levels/HeartedLevel.cs +++ b/ProjectLighthouse/Types/Levels/HeartedLevel.cs @@ -1,17 +1,22 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace LBPUnion.ProjectLighthouse.Types.Levels { - public class HeartedLevel { +namespace LBPUnion.ProjectLighthouse.Types.Levels +{ + public class HeartedLevel + { // ReSharper disable once UnusedMember.Global - [Key] public int HeartedLevelId { get; set; } + [Key] + public int HeartedLevelId { get; set; } public int UserId { get; set; } - [ForeignKey(nameof(UserId))] public User User { get; set; } + [ForeignKey(nameof(UserId))] + public User User { get; set; } public int SlotId { get; set; } - [ForeignKey(nameof(SlotId))] public Slot Slot { get; set; } + [ForeignKey(nameof(SlotId))] + public Slot Slot { get; set; } } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Levels/LevelTags.cs b/ProjectLighthouse/Types/Levels/LevelTags.cs index 3bb2a212..784985e4 100644 --- a/ProjectLighthouse/Types/Levels/LevelTags.cs +++ b/ProjectLighthouse/Types/Levels/LevelTags.cs @@ -1,12 +1,14 @@ using System.Diagnostics.CodeAnalysis; -namespace LBPUnion.ProjectLighthouse.Types.Levels { +namespace LBPUnion.ProjectLighthouse.Types.Levels +{ /// - /// A series of tags that can be applied to a level + /// A series of tags that can be applied to a level /// [SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "UnusedMember.Global")] - public enum LevelTags { + public enum LevelTags + { Brilliant, Beautiful, Funky, diff --git a/ProjectLighthouse/Types/Levels/QueuedLevel.cs b/ProjectLighthouse/Types/Levels/QueuedLevel.cs index 484f0c36..e238627e 100644 --- a/ProjectLighthouse/Types/Levels/QueuedLevel.cs +++ b/ProjectLighthouse/Types/Levels/QueuedLevel.cs @@ -1,16 +1,19 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace LBPUnion.ProjectLighthouse.Types.Levels { - public class QueuedLevel { +namespace LBPUnion.ProjectLighthouse.Types.Levels +{ + public class QueuedLevel + { // ReSharper disable once UnusedMember.Global - [Key] public int QueuedLevelId { get; set; } - + [Key] + public int QueuedLevelId { get; set; } + public int UserId { get; set; } [ForeignKey(nameof(UserId))] public User User { get; set; } - + public int SlotId { get; set; } [ForeignKey(nameof(SlotId))] diff --git a/ProjectLighthouse/Types/Levels/Slot.cs b/ProjectLighthouse/Types/Levels/Slot.cs index c6fe9f43..79b9d2fe 100644 --- a/ProjectLighthouse/Types/Levels/Slot.cs +++ b/ProjectLighthouse/Types/Levels/Slot.cs @@ -5,92 +5,103 @@ using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types.Profiles; -namespace LBPUnion.ProjectLighthouse.Types.Levels { +namespace LBPUnion.ProjectLighthouse.Types.Levels +{ /// - /// A LittleBigPlanet level. + /// A LittleBigPlanet level. /// - [XmlRoot("slot"), XmlType("slot")] - public class Slot { + [XmlRoot("slot")] + [XmlType("slot")] + public class Slot + { [XmlAttribute("type")] [NotMapped] public string Type { get; set; } [Key] - [XmlIgnore] + [XmlElement("id")] public int SlotId { get; set; } - [XmlElement("name")] public string Name { get; set; } - + [XmlElement("description")] public string Description { get; set; } - + [XmlElement("icon")] public string IconHash { get; set; } - + [XmlElement("rootLevel")] public string RootLevel { get; set; } public string ResourceCollection { get; set; } - + [NotMapped] [XmlElement("resource")] public string[] Resources { get => this.ResourceCollection.Split(","); set => this.ResourceCollection = string.Join(',', value); } - + [XmlIgnore] public int LocationId { get; set; } - [XmlIgnore] + [XmlIgnore] public int CreatorId { get; set; } [ForeignKey(nameof(CreatorId))] public User Creator { get; set; } /// - /// The location of the level on the creator's earth + /// The location of the level on the creator's earth /// [XmlElement("location")] [ForeignKey(nameof(LocationId))] public Location Location { get; set; } - + [XmlElement("initiallyLocked")] public bool InitiallyLocked { get; set; } - + [XmlElement("isSubLevel")] public bool SubLevel { get; set; } - + [XmlElement("isLBP1Only")] public bool Lbp1Only { get; set; } - + [XmlElement("shareable")] public int Shareable { get; set; } - + [XmlElement("authorLabels")] public string AuthorLabels { get; set; } - [XmlElement("background")] + [XmlElement("background")] public string BackgroundHash { get; set; } = ""; - + [XmlElement("minPlayers")] public int MinimumPlayers { get; set; } - + [XmlElement("maxPlayers")] public int MaximumPlayers { get; set; } - + [XmlElement("moveRequired")] public bool MoveRequired { get; set; } - public string SerializeResources() { - return this.Resources - .Aggregate("", (current, resource) => - current + LbpSerializer.StringElement("resource", resource)); + [XmlIgnore] + public long FirstUploaded { get; set; } + + [XmlIgnore] + public long LastUpdated { get; set; } + + [XmlIgnore] + public bool MMPick { get; set; } + + public string SerializeResources() + { + return this.Resources.Aggregate("", (current, resource) => current + LbpSerializer.StringElement("resource", resource)); } - public string Serialize() { + public string Serialize() + { string slotData = LbpSerializer.StringElement("name", this.Name) + LbpSerializer.StringElement("id", this.SlotId) + LbpSerializer.StringElement("game", 1) + @@ -107,8 +118,11 @@ namespace LBPUnion.ProjectLighthouse.Types.Levels { LbpSerializer.StringElement("background", this.BackgroundHash) + LbpSerializer.StringElement("minPlayers", this.MinimumPlayers) + LbpSerializer.StringElement("maxPlayers", this.MaximumPlayers) + - LbpSerializer.StringElement("moveRequired", this.MoveRequired); - + LbpSerializer.StringElement("moveRequired", this.MoveRequired) + + LbpSerializer.StringElement("firstPublished", this.FirstUploaded) + + LbpSerializer.StringElement("lastUpdated", this.LastUpdated) + + LbpSerializer.StringElement("mmpick", this.MMPick); + return LbpSerializer.TaggedStringElement("slot", slotData, "type", "user"); } } diff --git a/ProjectLighthouse/Types/LoginData.cs b/ProjectLighthouse/Types/LoginData.cs index 7cb1b5ff..5fcde0f1 100644 --- a/ProjectLighthouse/Types/LoginData.cs +++ b/ProjectLighthouse/Types/LoginData.cs @@ -1,39 +1,43 @@ +#nullable enable +using System; using System.IO; using System.Text; using LBPUnion.ProjectLighthouse.Helpers; -namespace LBPUnion.ProjectLighthouse.Types { - // This is all the information I can understand for now. More testing is required. - // Example data: - // - LBP2 digital, with the RPCN username `literally1984` - // POST /LITTLEBIGPLANETPS3_XML/login?applicationID=21414&languageID=1&lbp2=1&beta=0&titleID=NPUA80662&country=us - // !�0256333||x||��Y literally198bruUP9000-NPUA80662_008D - // - LBP2 digital, with the RPCN username `jvyden` - // POST /LITTLEBIGPLANETPS3_XML/login?applicationID=21414&languageID=1&lbp2=1&beta=0&titleID=NPUA80662&country=us - // !�0220333||/u||=0� jvydebruUP9000-NPUA80662_008D +namespace LBPUnion.ProjectLighthouse.Types +{ /// - /// The data sent from POST /LOGIN. + /// The data sent from POST /LOGIN. /// - public class LoginData { - public string Username { get; set; } -// public string GameVersion { get; set; } -// public int UnknownNumber { get; set; } // Seems to increment by 1000 every login attempt + public class LoginData + { - public static LoginData CreateFromString(string str) { - do { - str = str.Replace("\b", string.Empty); // Trim backspace characters - } while(str.Contains('\b')); + public static readonly string UsernamePrefix = Encoding.ASCII.GetString + ( + new byte[] + { + 0x04, 0x00, 0x20, + } + ); + + public string Username { get; set; } = null!; + + /// + /// Converts a X-I-5 Ticket into `LoginData`. + /// https://www.psdevwiki.com/ps3/X-I-5-Ticket + /// + public static LoginData? CreateFromString(string str) + { + str = str.Replace("\b", ""); // Remove backspace characters using MemoryStream ms = new(Encoding.ASCII.GetBytes(str)); using BinaryReader reader = new(ms); + if (!str.Contains(UsernamePrefix)) return null; + LoginData loginData = new(); - BinaryHelper.ReadUntilByte(reader, 0x20); // Skips to relevant part - -// byte[] endBytes = reader.ReadBytes((int)(ms.Length - reader.BaseStream.Position)); -// string end = Encoding.ASCII.GetString(endBytes); - + reader.BaseStream.Position = str.IndexOf(UsernamePrefix, StringComparison.Ordinal) + UsernamePrefix.Length; loginData.Username = BinaryHelper.ReadString(reader).Replace("\0", string.Empty); return loginData; diff --git a/ProjectLighthouse/Types/LoginResult.cs b/ProjectLighthouse/Types/LoginResult.cs index c0ed0997..bafb2416 100644 --- a/ProjectLighthouse/Types/LoginResult.cs +++ b/ProjectLighthouse/Types/LoginResult.cs @@ -2,23 +2,23 @@ using System.Collections.Generic; using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Serialization; -namespace LBPUnion.ProjectLighthouse.Types { +namespace LBPUnion.ProjectLighthouse.Types +{ /// - /// Response to POST /login + /// Response to POST /login /// - [XmlRoot("loginResult"), XmlType("loginResult")] - public class LoginResult { + [XmlRoot("loginResult")] + [XmlType("loginResult")] + public class LoginResult + { [XmlElement("authTicket")] public string AuthTicket { get; set; } [XmlElement("lbpEnvVer")] public string LbpEnvVer { get; set; } - public string Serialize() { - return LbpSerializer.Elements( - new KeyValuePair("authTicket", this.AuthTicket), - new KeyValuePair("lbpEnvVer", this.LbpEnvVer) - ); - } + public string Serialize() + => LbpSerializer.Elements + (new KeyValuePair("authTicket", this.AuthTicket), new KeyValuePair("lbpEnvVer", this.LbpEnvVer)); } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Match/IMatchData.cs b/ProjectLighthouse/Types/Match/IMatchData.cs index 435b08ef..b785b433 100644 --- a/ProjectLighthouse/Types/Match/IMatchData.cs +++ b/ProjectLighthouse/Types/Match/IMatchData.cs @@ -1,5 +1,5 @@ -namespace LBPUnion.ProjectLighthouse.Types.Match { - public interface IMatchData { - - } +namespace LBPUnion.ProjectLighthouse.Types.Match +{ + public interface IMatchData + {} } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Match/RoomState.cs b/ProjectLighthouse/Types/Match/RoomState.cs index 1cb53079..2cfc28e2 100644 --- a/ProjectLighthouse/Types/Match/RoomState.cs +++ b/ProjectLighthouse/Types/Match/RoomState.cs @@ -1,5 +1,7 @@ -namespace LBPUnion.ProjectLighthouse.Types.Match { - public enum RoomState { +namespace LBPUnion.ProjectLighthouse.Types.Match +{ + public enum RoomState + { Idle = 0, LookingForPlayersForLevel = 1, Unknown = 2, diff --git a/ProjectLighthouse/Types/Match/UpdateMyPlayerData.cs b/ProjectLighthouse/Types/Match/UpdateMyPlayerData.cs index fade3952..ff93dd2c 100644 --- a/ProjectLighthouse/Types/Match/UpdateMyPlayerData.cs +++ b/ProjectLighthouse/Types/Match/UpdateMyPlayerData.cs @@ -1,5 +1,7 @@ -namespace LBPUnion.ProjectLighthouse.Types.Match { - public class UpdateMyPlayerData : IMatchData { +namespace LBPUnion.ProjectLighthouse.Types.Match +{ + public class UpdateMyPlayerData : IMatchData + { public string Player; } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Match/UpdatePlayersInRoom.cs b/ProjectLighthouse/Types/Match/UpdatePlayersInRoom.cs index a689bcc2..a7752ec2 100644 --- a/ProjectLighthouse/Types/Match/UpdatePlayersInRoom.cs +++ b/ProjectLighthouse/Types/Match/UpdatePlayersInRoom.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; -namespace LBPUnion.ProjectLighthouse.Types.Match { - public class UpdatePlayersInRoom : IMatchData { +namespace LBPUnion.ProjectLighthouse.Types.Match +{ + public class UpdatePlayersInRoom : IMatchData + { public List Players; public List Reservations; } diff --git a/ProjectLighthouse/Types/News/NewsEntry.cs b/ProjectLighthouse/Types/News/NewsEntry.cs index aecf5b88..2ef65ecb 100644 --- a/ProjectLighthouse/Types/News/NewsEntry.cs +++ b/ProjectLighthouse/Types/News/NewsEntry.cs @@ -1,10 +1,12 @@ using LBPUnion.ProjectLighthouse.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.News { +namespace LBPUnion.ProjectLighthouse.Types.News +{ /// - /// Used on the info moon on LBP1. Broken for unknown reasons + /// Used on the info moon on LBP1. Broken for unknown reasons /// - public class NewsEntry { + public class NewsEntry + { public int Id { get; set; } public string Title { get; set; } public string Summary { get; set; } @@ -13,14 +15,13 @@ namespace LBPUnion.ProjectLighthouse.Types.News { public string Category { get; set; } public long Date { get; set; } - public string Serialize() { - return LbpSerializer.StringElement("id", this.Id) + - LbpSerializer.StringElement("title", this.Title) + - LbpSerializer.StringElement("summary", this.Summary) + - LbpSerializer.StringElement("text", this.Text) + - LbpSerializer.StringElement("date", this.Date) + - this.Image.Serialize() + - LbpSerializer.StringElement("category", this.Category); - } + public string Serialize() + => LbpSerializer.StringElement("id", this.Id) + + LbpSerializer.StringElement("title", this.Title) + + LbpSerializer.StringElement("summary", this.Summary) + + LbpSerializer.StringElement("text", this.Text) + + LbpSerializer.StringElement("date", this.Date) + + this.Image.Serialize() + + LbpSerializer.StringElement("category", this.Category); } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/News/NewsImage.cs b/ProjectLighthouse/Types/News/NewsImage.cs index 4e134ae0..f174afff 100644 --- a/ProjectLighthouse/Types/News/NewsImage.cs +++ b/ProjectLighthouse/Types/News/NewsImage.cs @@ -1,14 +1,13 @@ using LBPUnion.ProjectLighthouse.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.News { - public class NewsImage { +namespace LBPUnion.ProjectLighthouse.Types.News +{ + public class NewsImage + { public string Hash { get; set; } public string Alignment { get; set; } - public string Serialize() { - return LbpSerializer.StringElement("image", - LbpSerializer.StringElement("hash", this.Hash) + - LbpSerializer.StringElement("alignment", this.Alignment)); - } + public string Serialize() + => LbpSerializer.StringElement("image", LbpSerializer.StringElement("hash", this.Hash) + LbpSerializer.StringElement("alignment", this.Alignment)); } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Profiles/ClientsConnected.cs b/ProjectLighthouse/Types/Profiles/ClientsConnected.cs index 2241ca4e..be6c77fe 100644 --- a/ProjectLighthouse/Types/Profiles/ClientsConnected.cs +++ b/ProjectLighthouse/Types/Profiles/ClientsConnected.cs @@ -1,22 +1,26 @@ using LBPUnion.ProjectLighthouse.Serialization; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Types.Profiles { +namespace LBPUnion.ProjectLighthouse.Types.Profiles +{ [Keyless] - public class ClientsConnected { + public class ClientsConnected + { public bool Lbp1 { get; set; } public bool Lbp2 { get; set; } public bool LbpMe { get; set; } public bool Lbp3Ps3 { get; set; } public bool Lbp3Ps4 { get; set; } - public string Serialize() { - return LbpSerializer.StringElement("clientsConnected", + public string Serialize() + => LbpSerializer.StringElement + ( + "clientsConnected", LbpSerializer.StringElement("lbp1", this.Lbp1) + LbpSerializer.StringElement("lbp2", this.Lbp2) + LbpSerializer.StringElement("lbpme", this.LbpMe) + LbpSerializer.StringElement("lbp3ps3", this.Lbp3Ps3) + - LbpSerializer.StringElement("lbp3ps4", this.Lbp3Ps4)); - } + LbpSerializer.StringElement("lbp3ps4", this.Lbp3Ps4) + ); } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Profiles/Comment.cs b/ProjectLighthouse/Types/Profiles/Comment.cs index a10f2d16..f2260fe2 100644 --- a/ProjectLighthouse/Types/Profiles/Comment.cs +++ b/ProjectLighthouse/Types/Profiles/Comment.cs @@ -3,20 +3,23 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Profiles { - [XmlRoot("comment"), XmlType("comment")] - public class Comment { +namespace LBPUnion.ProjectLighthouse.Types.Profiles +{ + [XmlRoot("comment")] + [XmlType("comment")] + public class Comment + { [Key] [XmlAttribute("id")] public int CommentId { get; set; } - + public int PosterUserId { get; set; } - + public int TargetUserId { get; set; } [ForeignKey(nameof(PosterUserId))] public User Poster { get; set; } - + [ForeignKey(nameof(TargetUserId))] public User Target { get; set; } @@ -24,24 +27,22 @@ namespace LBPUnion.ProjectLighthouse.Types.Profiles { [XmlElement("message")] public string Message { get; set; } + public int ThumbsUp { get; set; } public int ThumbsDown { get; set; } - private string serialize() { - return LbpSerializer.StringElement("id", this.CommentId) + - LbpSerializer.StringElement("npHandle", this.Poster.Username) + - LbpSerializer.StringElement("timestamp", this.Timestamp) + - LbpSerializer.StringElement("message", this.Message) + - LbpSerializer.StringElement("thumbsup", this.ThumbsUp) + - LbpSerializer.StringElement("thumbsdown", this.ThumbsDown); - } + private string serialize() + => LbpSerializer.StringElement("id", this.CommentId) + + LbpSerializer.StringElement("npHandle", this.Poster.Username) + + LbpSerializer.StringElement("timestamp", this.Timestamp) + + LbpSerializer.StringElement("message", this.Message) + + LbpSerializer.StringElement("thumbsup", this.ThumbsUp) + + LbpSerializer.StringElement("thumbsdown", this.ThumbsDown); - public string Serialize(int yourThumb) { - return LbpSerializer.StringElement("comment", this.serialize() + LbpSerializer.StringElement("yourthumb", yourThumb)); - } + public string Serialize + (int yourThumb) + => LbpSerializer.StringElement("comment", this.serialize() + LbpSerializer.StringElement("yourthumb", yourThumb)); - public string Serialize() { - return LbpSerializer.StringElement("comment", this.serialize()); - } + public string Serialize() => LbpSerializer.StringElement("comment", this.serialize()); } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Profiles/LastMatch.cs b/ProjectLighthouse/Types/Profiles/LastMatch.cs index 0513ca78..ec637c14 100644 --- a/ProjectLighthouse/Types/Profiles/LastMatch.cs +++ b/ProjectLighthouse/Types/Profiles/LastMatch.cs @@ -1,8 +1,12 @@ using System.ComponentModel.DataAnnotations; -namespace LBPUnion.ProjectLighthouse.Types.Profiles { - public class LastMatch { - [Key] public int UserId { get; set; } +namespace LBPUnion.ProjectLighthouse.Types.Profiles +{ + public class LastMatch + { + [Key] + public int UserId { get; set; } + public long Timestamp { get; set; } } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Profiles/Location.cs b/ProjectLighthouse/Types/Profiles/Location.cs index cfaefa68..139565e6 100644 --- a/ProjectLighthouse/Types/Profiles/Location.cs +++ b/ProjectLighthouse/Types/Profiles/Location.cs @@ -1,17 +1,24 @@ +using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Profiles { +namespace LBPUnion.ProjectLighthouse.Types.Profiles +{ /// - /// The location of a slot on a planet. + /// The location of a slot on a planet. /// - public class Location { + [XmlRoot("location")] + [XmlType("location")] + public class Location + { + [XmlIgnore] public int Id { get; set; } + + [XmlElement("x")] public int X { get; set; } + + [XmlElement("y")] public int Y { get; set; } - public string Serialize() { - return LbpSerializer.StringElement("x", this.X) + - LbpSerializer.StringElement("y", this.Y); - } + public string Serialize() => LbpSerializer.StringElement("x", this.X) + LbpSerializer.StringElement("y", this.Y); } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/ResourceList.cs b/ProjectLighthouse/Types/ResourceList.cs index 7e293cd8..5af4425a 100644 --- a/ProjectLighthouse/Types/ResourceList.cs +++ b/ProjectLighthouse/Types/ResourceList.cs @@ -1,9 +1,12 @@ using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types { - [XmlRoot("resources"), XmlType("resources")] - public class ResourceList { - [XmlElement("resource")] +namespace LBPUnion.ProjectLighthouse.Types +{ + [XmlRoot("resources")] + [XmlType("resources")] + public class ResourceList + { + [XmlElement("resource")] public string[] Resources; } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Settings/PrivacySettings.cs b/ProjectLighthouse/Types/Settings/PrivacySettings.cs index c381a66d..ac636c5d 100644 --- a/ProjectLighthouse/Types/Settings/PrivacySettings.cs +++ b/ProjectLighthouse/Types/Settings/PrivacySettings.cs @@ -1,15 +1,17 @@ using LBPUnion.ProjectLighthouse.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Settings { - public class PrivacySettings { +namespace LBPUnion.ProjectLighthouse.Types.Settings +{ + public class PrivacySettings + { public string LevelVisibility { get; set; } public string ProfileVisibility { get; set; } - public string Serialize() { - return LbpSerializer.StringElement("privacySettings", - LbpSerializer.StringElement("levelVisibility", this.LevelVisibility) + - LbpSerializer.StringElement("profileVisibility", this.ProfileVisibility) + public string Serialize() + => LbpSerializer.StringElement + ( + "privacySettings", + LbpSerializer.StringElement("levelVisibility", this.LevelVisibility) + LbpSerializer.StringElement("profileVisibility", this.ProfileVisibility) ); - } } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Settings/ServerSettings.cs b/ProjectLighthouse/Types/Settings/ServerSettings.cs index 1e456c23..daaa3e95 100644 --- a/ProjectLighthouse/Types/Settings/ServerSettings.cs +++ b/ProjectLighthouse/Types/Settings/ServerSettings.cs @@ -3,23 +3,25 @@ using System; using Kettu; using LBPUnion.ProjectLighthouse.Logging; -namespace LBPUnion.ProjectLighthouse.Types.Settings { - public static class ServerSettings { +namespace LBPUnion.ProjectLighthouse.Types.Settings +{ + public static class ServerSettings + { /// - /// The maximum amount of slots allowed on users' earth + /// The maximum amount of slots allowed on users' earth /// - public const int EntitledSlots = int.MaxValue; + public const int EntitledSlots = 50; - public const int ListsQuota = 20; + public const int ListsQuota = 50; public const string ServerName = "ProjectLighthouse"; private static string? dbConnectionString; + public static string DbConnectionString { get { - if(dbConnectionString == null) { - return dbConnectionString = Environment.GetEnvironmentVariable("LIGHTHOUSE_DB_CONNECTION_STRING") ?? ""; - } + if (dbConnectionString == null) return dbConnectionString = Environment.GetEnvironmentVariable("LIGHTHOUSE_DB_CONNECTION_STRING") ?? ""; + return dbConnectionString; } set => dbConnectionString = value; @@ -27,10 +29,12 @@ namespace LBPUnion.ProjectLighthouse.Types.Settings { public static bool DbConnected { get { - try { + try + { return new Database().Database.CanConnect(); } - catch(Exception e) { + catch(Exception e) + { Logger.Log(e.ToString(), LoggerLevelDatabase.Instance); return false; } diff --git a/ProjectLighthouse/Types/Token.cs b/ProjectLighthouse/Types/Token.cs index 9232de0c..979459f1 100644 --- a/ProjectLighthouse/Types/Token.cs +++ b/ProjectLighthouse/Types/Token.cs @@ -1,9 +1,13 @@ using System.ComponentModel.DataAnnotations; -namespace LBPUnion.ProjectLighthouse.Types { - public class Token { +namespace LBPUnion.ProjectLighthouse.Types +{ + public class Token + { // ReSharper disable once UnusedMember.Global - [Key] public int TokenId { get; set; } + [Key] + public int TokenId { get; set; } + public int UserId { get; set; } public string UserToken { get; set; } } diff --git a/ProjectLighthouse/Types/User.cs b/ProjectLighthouse/Types/User.cs index 2d77f692..8d16af5d 100644 --- a/ProjectLighthouse/Types/User.cs +++ b/ProjectLighthouse/Types/User.cs @@ -1,10 +1,16 @@ using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types.Profiles; using LBPUnion.ProjectLighthouse.Types.Settings; -namespace LBPUnion.ProjectLighthouse.Types { - public class User { +namespace LBPUnion.ProjectLighthouse.Types +{ + public class User + { + +// [NotMapped] + public readonly ClientsConnected ClientsConnected = new(); public int UserId { get; set; } public string Username { get; set; } public string IconHash { get; set; } @@ -13,21 +19,22 @@ namespace LBPUnion.ProjectLighthouse.Types { public int HeartCount { get; set; } public string YayHash { get; set; } public string BooHash { get; set; } - + /// - /// A user-customizable biography shown on the profile card + /// A user-customizable biography shown on the profile card /// public string Biography { get; set; } + public int ReviewCount { get; set; } public int CommentCount { get; set; } public int PhotosByMeCount { get; set; } public int PhotosWithMeCount { get; set; } public bool CommentsEnabled { get; set; } - + public int LocationId { get; set; } /// - /// The location of the profile card on the user's earth + /// The location of the profile card on the user's earth /// [ForeignKey("LocationId")] public Location Location { get; set; } @@ -41,50 +48,9 @@ namespace LBPUnion.ProjectLighthouse.Types { public int StaffChallengeBronzeCount { get; set; } public string PlanetHash { get; set; } = ""; - -// [NotMapped] - public readonly ClientsConnected ClientsConnected = new(); - - #region Slots - /// - /// The number of used slots on the earth - /// - public int UsedSlots { get; set; } - - /// - /// The number of slots remaining on the earth - /// - public int FreeSlots => ServerSettings.EntitledSlots - this.UsedSlots; - - private static readonly string[] slotTypes = { -// "lbp1", - "lbp2", - "lbp3", - "crossControl", - }; - - private string SerializeSlots() { - string slots = string.Empty; - - slots += LbpSerializer.StringElement("lbp1UsedSlots", this.UsedSlots); - slots += LbpSerializer.StringElement("entitledSlots", ServerSettings.EntitledSlots); - slots += LbpSerializer.StringElement("freeSlots", this.FreeSlots); - - foreach(string slotType in slotTypes) { - slots += LbpSerializer.StringElement(slotType + "UsedSlots", this.UsedSlots); - slots += LbpSerializer.StringElement(slotType + "EntitledSlots", ServerSettings.EntitledSlots); - // ReSharper disable once StringLiteralTypo - slots += LbpSerializer.StringElement(slotType + slotType == "crossControl" ? "PurchsedSlots" : "PurchasedSlots", 0); - slots += LbpSerializer.StringElement(slotType + "FreeSlots", this.FreeSlots); - } - return slots; - - } - - #endregion Slots - - public string Serialize() { + public string Serialize() + { string user = LbpSerializer.TaggedStringElement("npHandle", this.Username, "icon", this.IconHash) + LbpSerializer.StringElement("game", this.Game) + this.SerializeSlots() + @@ -110,8 +76,55 @@ namespace LBPUnion.ProjectLighthouse.Types { LbpSerializer.StringElement("planets", this.PlanetHash) + LbpSerializer.BlankElement("photos") + this.ClientsConnected.Serialize(); - + return LbpSerializer.TaggedStringElement("user", user, "type", "user"); } + + #region Slots + + /// + /// The number of used slots on the earth + /// + [NotMapped] + public int UsedSlots { + get { + using Database database = new(); + return database.Slots.Count(s => s.CreatorId == this.UserId); + } + } + + /// + /// The number of slots remaining on the earth + /// + public int FreeSlots => ServerSettings.EntitledSlots - this.UsedSlots; + + private static readonly string[] slotTypes = + { +// "lbp1", + "lbp2", "lbp3", "crossControl", + }; + + private string SerializeSlots() + { + string slots = string.Empty; + + slots += LbpSerializer.StringElement("lbp1UsedSlots", this.UsedSlots); + slots += LbpSerializer.StringElement("entitledSlots", ServerSettings.EntitledSlots); + slots += LbpSerializer.StringElement("freeSlots", this.FreeSlots); + + foreach (string slotType in slotTypes) + { + slots += LbpSerializer.StringElement(slotType + "UsedSlots", this.UsedSlots); + slots += LbpSerializer.StringElement(slotType + "EntitledSlots", ServerSettings.EntitledSlots); + // ReSharper disable once StringLiteralTypo + slots += LbpSerializer.StringElement(slotType + slotType == "crossControl" ? "PurchsedSlots" : "PurchasedSlots", 0); + slots += LbpSerializer.StringElement(slotType + "FreeSlots", this.FreeSlots); + } + return slots; + + } + + #endregion Slots + } } \ No newline at end of file diff --git a/README.md b/README.md index dfa80011..8867d5e9 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,84 @@ # Project Lighthouse + Project Lighthouse is an umbrella project for all work to investigate and develop private servers for LittleBigPlanet. This project is the main server component that LittleBigPlanet games connect to. ## WARNING! -This is beta software, and thus is not ready for public use yet. -We're not responsible if someone connects and hacks your entire machine and deletes all your files. + +This is beta software, and thus is not ready for public use yet. We're not responsible if someone connects and hacks +your entire machine and deletes all your files. That said, feel free to develop privately! ## Building + This will be written when we're out of beta. Consider this your barrier to entry ;). ## Running -Lighthouse requires a MySQL database at this time. -For Linux users running docker, one can be set up using the `docker-compose.yml` file in the root of the project folder. -Next, make sure the `LIGHTHOUSE_DB_CONNECTION_STRING` environment variable is set correctly. -By default, it is `server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse`. If you are running the database via -the above `docker-compose.yml` you shouldn't need to change this. For other development/especially production environments +Lighthouse requires a MySQL database at this time. For Linux users running docker, one can be set up using +the `docker-compose.yml` file in the root of the project folder. + +Next, make sure the `LIGHTHOUSE_DB_CONNECTION_STRING` environment variable is set correctly. By default, it +is `server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse`. If you are running the database via the +above `docker-compose.yml` you shouldn't need to change this. For other development/especially production environments you will need to change this. Once you've gotten MySQL running you can run Lighthouse. It will take care of the rest. ## Connecting -PS3 is difficult to set up, so I will be going over how to set up RPCS3 instead. A guide will be coming for PS3 closer to release. -You can also follow this guide if you want to learn how to modify your EBOOT. -*Note: This requires a modified copy of RPCS3. You can find a working patch [here](https://gist.github.com/jvyden/0d9619f7dd3dbc49f7583486bdacad75).* +PS3 is difficult to set up, so I will be going over how to set up RPCS3 instead. A guide will be coming for PS3 closer +to release. You can also follow this guide if you want to learn how to modify your EBOOT. -Start by getting a copy of LittleBigPlanet 2 installed. It can be digital (NPUA80662) or disc (BCUS98245). -I won't get into how because if you got this far you should already know what you're doing. For those that don't, the [RPCS3 Quickstart Guide](https://rpcs3.net/quickstart) should cover it. +*Note: This requires a modified copy of RPCS3. You can find a working +patch [here](https://gist.github.com/jvyden/0d9619f7dd3dbc49f7583486bdacad75).* -Next, download [UnionPatcher](https://github.com/LBPUnion/UnionPatcher/). Binaries can be found by reading the README.md file. +Start by getting a copy of LittleBigPlanet 2 installed. It can be digital (NPUA80662) or disc (BCUS98245). I won't get +into how because if you got this far you should already know what you're doing. For those that don't, +the [RPCS3 Quickstart Guide](https://rpcs3.net/quickstart) should cover it. -You should have everything you need now, so open up RPCS3 and go to Utilities -> Decrypt PS3 Binaries. Point this to `rpcs3/dev_hdd0/game/(title id)/USRDIR/EBOOT.BIN`. +Next, download [UnionPatcher](https://github.com/LBPUnion/UnionPatcher/). Binaries can be found by reading the README.md +file. -This should give you a file named `EBOOT.elf` in the same folder. Next, fire up UnionPatcher (making sure to select the correct project to start, e.g. on Mac launch `UnionPatcher.Gui.MacOS`.) +You should have everything you need now, so open up RPCS3 and go to Utilities -> Decrypt PS3 Binaries. Point this +to `rpcs3/dev_hdd0/game/(title id)/USRDIR/EBOOT.BIN`. -Now that you have your decrypted eboot, open UnionPatcher and select the `EBOOT.elf` you got earlier in the top box, enter `http://localhost:10060/LITTLEBIGPLANETPS3_XML` in the second, and the output filename in the third. -For this guide I'll use `EBOOTlocalhost.elf`. +This should give you a file named `EBOOT.elf` in the same folder. Next, fire up UnionPatcher (making sure to select the +correct project to start, e.g. on Mac launch `UnionPatcher.Gui.MacOS`.) + +Now that you have your decrypted eboot, open UnionPatcher and select the `EBOOT.elf` you got earlier in the top box, +enter `http://localhost:10060/LITTLEBIGPLANETPS3_XML` in the second, and the output filename in the third. For this +guide I'll use `EBOOTlocalhost.elf`. Now, copy the `EBOOTlocalhost.elf` file to where you got your `EBOOT.elf` file from, and you're now good to go. To launch the game with the patched EBOOT, open up RPCS3, go to File, Boot SELF/ELF, and open up `EBOOTlocalhost.elf`. -Assuming you are running the patched version of RPCS3, you patched the file correctly, the database is migrated, and Lighthouse is running, the game should now connect. +Assuming you are running the patched version of RPCS3, you patched the file correctly, the database is migrated, and +Lighthouse is running, the game should now connect. Finally, take a break. Chances are that took a while. ## Contributing Tips + ### Database + Some modifications may require updates to the database schema. You can automatically create a migration file by: 1. Making sure the tools are installed. You can do this by running `dotnet tool restore`. 2. Making sure `LIGHTHOUSE_DB_CONNECTION_STRING` is set correctly. See the `Running` section for more details. -3. Making your changes to the database. I won't cover this since if you're making database changes you should know what you're doing. +3. Making your changes to the database. I won't cover this since if you're making database changes you should know what + you're doing. 4. Running `dotnet ef migrations add --project ProjectLighthouse`. + +## Compatibility across games and platforms + +| Game | Console (PS3/Vita) | Emulator (RPCS3) | Next-Gen (PS4/PS5) | +|----------|------------------------|------------------------------------------------|--------------------| +| LBP1 | Somewhat compatible | Incompatible, crashes on entering pod computer | N/A | +| LBP2 | Compatible | Compatible with patched RPCS3 | N/A | +| LBP3 | Somewhat compatible | Somewhat compatible with workaround | Incompatible | +| LBP Vita | Potentially compatible | N/A | N/A | + +Project Lighthouse is still a heavy work in progress, so this is subject to change at any point. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 973902f2..0f507cfe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,6 @@ services: - '3306' # Expose port to localhost:3306 volumes: - lighthouse-db:/var/lib/mysql - + volumes: lighthouse-db: \ No newline at end of file