diff --git a/.idea/.idea.ProjectLighthouse/.idea/discord.xml b/.idea/.idea.ProjectLighthouse/.idea/discord.xml index d8e95616..62733844 100644 --- a/.idea/.idea.ProjectLighthouse/.idea/discord.xml +++ b/.idea/.idea.ProjectLighthouse/.idea/discord.xml @@ -2,6 +2,8 @@ \ No newline at end of file diff --git a/ProjectLighthouse.Tests.GameApiTests/DatabaseTests.cs b/ProjectLighthouse.Tests.GameApiTests/DatabaseTests.cs index 5aa82220..33da6dcb 100644 --- a/ProjectLighthouse.Tests.GameApiTests/DatabaseTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/DatabaseTests.cs @@ -16,8 +16,8 @@ public class DatabaseTests : LighthouseServerTest await using Database database = new(); int rand = new Random().Next(); - User userA = await database.CreateUser("createUserTwiceTest" + rand, HashHelper.GenerateAuthToken()); - User userB = await database.CreateUser("createUserTwiceTest" + rand, HashHelper.GenerateAuthToken()); + User userA = await database.CreateUser("unitTestUser" + rand, HashHelper.GenerateAuthToken()); + User userB = await database.CreateUser("unitTestUser" + rand, HashHelper.GenerateAuthToken()); Assert.NotNull(userA); Assert.NotNull(userB); diff --git a/ProjectLighthouse.sln.DotSettings b/ProjectLighthouse.sln.DotSettings index b64de555..5d18923a 100644 --- a/ProjectLighthouse.sln.DotSettings +++ b/ProjectLighthouse.sln.DotSettings @@ -133,6 +133,7 @@ True True True + True True True True @@ -144,4 +145,5 @@ True True True + True True \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/Api/UserEndpoints.cs b/ProjectLighthouse/Controllers/Api/UserEndpoints.cs index 7fc505eb..63510326 100644 --- a/ProjectLighthouse/Controllers/Api/UserEndpoints.cs +++ b/ProjectLighthouse/Controllers/Api/UserEndpoints.cs @@ -1,10 +1,13 @@ #nullable enable using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Profiles; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +// ReSharper disable RouteTemplates.ActionRoutePrefixCanBeExtractedToControllerRoute + namespace LBPUnion.ProjectLighthouse.Controllers.Api; /// @@ -36,4 +39,21 @@ public class UserEndpoints : ApiEndpointController return this.Ok(user); } + + /// + /// Gets a user and their information from the database. + /// + /// The ID of the user + /// The user's status + /// The user's status, if successful. + /// The user could not be found. + [HttpGet("user/{id:int}/status")] + [ProducesResponseType(typeof(UserStatus), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetUserStatus(int id) + { + UserStatus userStatus = new(this.database, id); + + return this.Ok(userStatus); + } } \ No newline at end of file diff --git a/ProjectLighthouse/Controllers/GameApi/CommentController.cs b/ProjectLighthouse/Controllers/GameApi/CommentController.cs index cfca4f09..e2ca484c 100644 --- a/ProjectLighthouse/Controllers/GameApi/CommentController.cs +++ b/ProjectLighthouse/Controllers/GameApi/CommentController.cs @@ -5,7 +5,6 @@ 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.Levels; @@ -39,14 +38,13 @@ public class CommentController : ControllerBase return this.Ok(); } - [HttpGet("comments/user/{slotId:int}")] [HttpGet("userComments/{username}")] public async Task GetComments([FromQuery] int pageStart, [FromQuery] int pageSize, string? username, int? slotId) { User? user = await this.database.UserFromGameRequest(this.Request); if (user == null) return this.StatusCode(403, ""); - + int targetId = slotId.GetValueOrDefault(); CommentType type = CommentType.Level; if (!string.IsNullOrWhiteSpace(username)) @@ -55,24 +53,24 @@ public class CommentController : ControllerBase type = CommentType.Profile; } - List comments = await this.database.Comments - .Include(c => c.Poster) + List comments = await this.database.Comments.Include + (c => c.Poster) .Where(c => c.TargetId == targetId && c.Type == type) .OrderByDescending(c => c.Timestamp) .Skip(pageStart - 1) - .Take(Math.Min(pageSize, - 30)) + .Take(Math.Min(pageSize, 30)) .ToListAsync(); - string outputXml = comments.Aggregate(string.Empty, (current, comment) => current + - comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result)); + string outputXml = comments.Aggregate + (string.Empty, (current, comment) => current + comment.Serialize(this.getReaction(user.UserId, comment.CommentId).Result)); return this.Ok(LbpSerializer.StringElement("comments", outputXml)); } - public async Task getReaction(int userId, int commentId) + private async Task getReaction(int userId, int commentId) { Reaction? reaction = await this.database.Reactions.FirstOrDefaultAsync(r => r.UserId == userId && r.TargetId == commentId); if (reaction == null) return 0; + return reaction.Rating; } @@ -80,11 +78,11 @@ public class CommentController : ControllerBase [HttpPost("postComment/user/{slotId:int}")] public async Task PostComment(string? username, int? slotId) { - this.Request.Body.Position = 0; + this.Request.Body.Position = 0; string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); XmlSerializer serializer = new(typeof(Comment)); - Comment? comment = (Comment?) serializer.Deserialize(new StringReader(bodyString)); + Comment? comment = (Comment?)serializer.Deserialize(new StringReader(bodyString)); CommentType type = (slotId.GetValueOrDefault() == 0 ? CommentType.Profile : CommentType.Level); @@ -112,6 +110,7 @@ public class CommentController : ControllerBase Comment? comment = await this.database.Comments.FirstOrDefaultAsync(c => c.CommentId == commentId); if (comment == null) return this.NotFound(); + // if you are not the poster if (comment.PosterUserId != user.UserId) { diff --git a/ProjectLighthouse/Controllers/GameApi/LoginController.cs b/ProjectLighthouse/Controllers/GameApi/LoginController.cs index 739e5322..20ec33ae 100644 --- a/ProjectLighthouse/Controllers/GameApi/LoginController.cs +++ b/ProjectLighthouse/Controllers/GameApi/LoginController.cs @@ -34,10 +34,6 @@ public class LoginController : ControllerBase await this.Request.Body.CopyToAsync(ms); byte[] loginData = ms.ToArray(); - #if DEBUG - await IOFile.WriteAllBytesAsync($"npTicket-{TimestampHelper.TimestampMillis}.txt", loginData); - #endif - NPTicket? npTicket; try { @@ -145,7 +141,7 @@ public class LoginController : ControllerBase await this.database.SaveChangesAsync(); // Create a new room on LBP2/3/Vita - if (token.GameVersion != GameVersion.LittleBigPlanet1) RoomHelper.CreateRoom(user, token.GameVersion); + if (token.GameVersion != GameVersion.LittleBigPlanet1) RoomHelper.CreateRoom(user, token.GameVersion, token.Platform); return this.Ok ( diff --git a/ProjectLighthouse/Controllers/GameApi/Matching/MatchController.cs b/ProjectLighthouse/Controllers/GameApi/Matching/MatchController.cs index 7ba10605..d250fb44 100644 --- a/ProjectLighthouse/Controllers/GameApi/Matching/MatchController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Matching/MatchController.cs @@ -74,14 +74,14 @@ public class MatchController : ControllerBase #endregion - await LastContactHelper.SetLastContact(user, gameToken.GameVersion); + await LastContactHelper.SetLastContact(user, gameToken.GameVersion, gameToken.Platform); #region Process match data if (matchData is UpdateMyPlayerData playerData) { MatchHelper.SetUserLocation(user.UserId, gameToken.UserLocation); - Room? room = RoomHelper.FindRoomByUser(user, gameToken.GameVersion, true); + Room? room = RoomHelper.FindRoomByUser(user, gameToken.GameVersion, gameToken.Platform, true); if (playerData.RoomState != null) if (room != null && Equals(room.Host, user)) @@ -90,7 +90,7 @@ public class MatchController : ControllerBase if (matchData is FindBestRoom && MatchHelper.UserLocations.Count > 1) { - FindBestRoomResponse? response = RoomHelper.FindBestRoom(user, gameToken.GameVersion, gameToken.UserLocation); + FindBestRoomResponse? response = RoomHelper.FindBestRoom(user, gameToken.GameVersion, gameToken.Platform, gameToken.UserLocation); if (response == null) return this.NotFound(); @@ -112,7 +112,7 @@ public class MatchController : ControllerBase } // Create a new one as requested - RoomHelper.CreateRoom(users, gameToken.GameVersion, createRoom.RoomSlot); + RoomHelper.CreateRoom(users, gameToken.GameVersion, gameToken.Platform, createRoom.RoomSlot); } if (matchData is UpdatePlayersInRoom updatePlayersInRoom) diff --git a/ProjectLighthouse/Controllers/GameApi/Slots/PublishController.cs b/ProjectLighthouse/Controllers/GameApi/Slots/PublishController.cs index b7128d8d..bcfdd34a 100644 --- a/ProjectLighthouse/Controllers/GameApi/Slots/PublishController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Slots/PublishController.cs @@ -33,10 +33,13 @@ public class PublishController : ControllerBase [HttpPost("startPublish")] public async Task StartPublish() { - User? user = await this.database.UserFromGameRequest(this.Request); - if (user == null) return this.StatusCode(403, ""); + (User, GameToken)? userAndToken = await this.database.UserAndGameTokenFromRequest(this.Request); - if (user.UsedSlots >= ServerSettings.Instance.EntitledSlots) return this.BadRequest(); + if (userAndToken == null) return this.StatusCode(403, ""); + + // ReSharper disable once PossibleInvalidOperationException + User user = userAndToken.Value.Item1; + GameToken gameToken = userAndToken.Value.Item2; Slot? slot = await this.getSlotFromBody(); if (slot == null) return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded @@ -52,6 +55,10 @@ public class PublishController : ControllerBase if (oldSlot == null) return this.NotFound(); if (oldSlot.CreatorId != user.UserId) return this.BadRequest(); } + else if (user.GetUsedSlotsForGame(gameToken.GameVersion, database) > ServerSettings.Instance.EntitledSlots) + { + return this.StatusCode(403, ""); + } slot.ResourceCollection += "," + slot.IconHash; // tells LBP to upload icon after we process resources here @@ -76,9 +83,6 @@ public class PublishController : ControllerBase // ReSharper disable once PossibleInvalidOperationException User user = userAndToken.Value.Item1; GameToken gameToken = userAndToken.Value.Item2; - - if (user.UsedSlots >= ServerSettings.Instance.EntitledSlots) return this.BadRequest(); - Slot? slot = await this.getSlotFromBody(); if (slot?.Location == null) return this.BadRequest(); @@ -133,6 +137,11 @@ public class PublishController : ControllerBase return this.Ok(oldSlot.Serialize(gameToken.GameVersion)); } + if (user.GetUsedSlotsForGame(gameToken.GameVersion, database) > ServerSettings.Instance.EntitledSlots) + { + return this.StatusCode(403, ""); + } + //TODO: parse location in body Location l = new() { diff --git a/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs b/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs index e7ad7d7f..71bbc3f4 100644 --- a/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Slots/ReviewController.cs @@ -17,7 +17,7 @@ namespace LBPUnion.ProjectLighthouse.Controllers.GameApi.Slots; [ApiController] [Route("LITTLEBIGPLANETPS3_XML/")] -[Produces("text/plain")] +[Produces("text/xml")] public class ReviewController : ControllerBase { private readonly Database database; @@ -141,51 +141,19 @@ public class ReviewController : ControllerBase GameVersion gameVersion = gameToken.GameVersion; - Random rand = new(); - - Review? yourReview = await this.database.Reviews.FirstOrDefaultAsync - (r => r.ReviewerId == user.UserId && r.SlotId == slotId && r.Slot.GameVersion <= gameVersion); - - VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync - (v => v.UserId == user.UserId && v.SlotId == slotId && v.Slot.GameVersion <= gameVersion); - Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.SlotId == slotId); if (slot == null) return this.BadRequest(); - bool canNowReviewLevel = slot.CreatorId != user.UserId && visitedLevel != null && yourReview == null; - if (canNowReviewLevel) - { - RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync - (r => r.UserId == user.UserId && r.SlotId == slotId && r.Slot.GameVersion <= gameVersion); - - yourReview = new Review(); - yourReview.ReviewerId = user.UserId; - yourReview.Reviewer = user; - yourReview.Thumb = ratedLevel?.Rating ?? 0; - yourReview.Slot = slot; - yourReview.SlotId = slotId; - yourReview.Deleted = false; - yourReview.DeletedBy = DeletedBy.None; - yourReview.Text = "You haven't reviewed this level yet. Edit this to write one!"; - yourReview.LabelCollection = ""; - yourReview.Timestamp = TimeHelper.UnixTimeMilliseconds(); - } - IQueryable reviews = this.database.Reviews.Where(r => r.SlotId == slotId && r.Slot.GameVersion <= gameVersion) .Include(r => r.Reviewer) .Include(r => r.Slot) .OrderByDescending(r => r.ThumbsUp) - .ThenByDescending(_ => EF.Functions.Random()) + .ThenByDescending(r => r.Timestamp) .Skip(pageStart - 1) .Take(pageSize); - IEnumerable prependedReviews; - if (canNowReviewLevel) // this can only be true if you have not posted a review but have visited the level - // prepend the fake review to the top of the list to be easily edited - prependedReviews = reviews.ToList().Prepend(yourReview); - else prependedReviews = reviews.ToList(); - string inner = prependedReviews.Aggregate + string inner = reviews.ToList().Aggregate ( string.Empty, (current, review) => diff --git a/ProjectLighthouse/Controllers/GameApi/Slots/ScoreController.cs b/ProjectLighthouse/Controllers/GameApi/Slots/ScoreController.cs index e92785ba..95df5843 100644 --- a/ProjectLighthouse/Controllers/GameApi/Slots/ScoreController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Slots/ScoreController.cs @@ -64,7 +64,9 @@ public class ScoreController : ControllerBase break; } - IQueryable existingScore = this.database.Scores.Where(s => s.SlotId == score.SlotId && s.PlayerIdCollection == score.PlayerIdCollection); + IQueryable existingScore = this.database.Scores.Where(s => s.SlotId == score.SlotId) + .Where(s => s.PlayerIdCollection == score.PlayerIdCollection) + .Where(s => s.Type == score.Type); if (existingScore.Any()) { @@ -80,7 +82,7 @@ public class ScoreController : ControllerBase await this.database.SaveChangesAsync(); - string myRanking = this.getScores(score.SlotId, score.Type, user); + string myRanking = this.getScores(score.SlotId, score.Type, user, -1, 5, "scoreboardSegment"); return this.Ok(myRanking); } @@ -103,7 +105,7 @@ public class ScoreController : ControllerBase } [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] - private string getScores(int slotId, int type, User user, int pageStart = -1, int pageSize = 5) + private string getScores(int slotId, int type, User user, int pageStart = -1, int pageSize = 5, string rootName = "scores") { // This is hella ugly but it technically assigns the proper rank to a score // var needed for Anonymous type returned from SELECT @@ -136,11 +138,11 @@ public class ScoreController : ControllerBase ); string res; - if (myScore == null) res = LbpSerializer.StringElement("scores", serializedScores); + if (myScore == null) res = LbpSerializer.StringElement(rootName, serializedScores); else res = LbpSerializer.TaggedStringElement ( - "scores", + rootName, serializedScores, new Dictionary { diff --git a/ProjectLighthouse/Controllers/GameApi/Slots/SlotsController.cs b/ProjectLighthouse/Controllers/GameApi/Slots/SlotsController.cs index 04d94441..9d89570a 100644 --- a/ProjectLighthouse/Controllers/GameApi/Slots/SlotsController.cs +++ b/ProjectLighthouse/Controllers/GameApi/Slots/SlotsController.cs @@ -4,9 +4,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Helpers.Extensions; using LBPUnion.ProjectLighthouse.Serialization; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Levels; +using LBPUnion.ProjectLighthouse.Types.Reviews; using LBPUnion.ProjectLighthouse.Types.Settings; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -24,18 +26,6 @@ public class SlotsController : ControllerBase this.database = database; } - private IQueryable getSlots(GameVersion gameVersion) - { - IQueryable query = this.database.Slots.Include(s => s.Creator).Include(s => s.Location); - - if (gameVersion == GameVersion.LittleBigPlanetVita || gameVersion == GameVersion.LittleBigPlanetPSP || gameVersion == GameVersion.Unknown) - { - return query.Where(s => s.GameVersion == gameVersion && !s.SubLevel); - } - - return query.Where(s => s.GameVersion <= gameVersion && !s.SubLevel); - } - [HttpGet("slots/by")] public async Task SlotsBy([FromQuery] string u, [FromQuery] int pageStart, [FromQuery] int pageSize) { @@ -49,8 +39,7 @@ public class SlotsController : ControllerBase string response = Enumerable.Aggregate ( - this.getSlots - (gameVersion) + this.database.Slots.ByGameVersion(gameVersion, token.UserId == user.UserId) .Where(s => s.Creator!.Username == user.Username) .Skip(pageStart - 1) .Take(Math.Min(pageSize, ServerSettings.Instance.EntitledSlots)), @@ -88,13 +77,14 @@ public class SlotsController : ControllerBase GameVersion gameVersion = token.GameVersion; - Slot? slot = await this.getSlots(gameVersion).FirstOrDefaultAsync(s => s.SlotId == id); + Slot? slot = await this.database.Slots.ByGameVersion(gameVersion, true).FirstOrDefaultAsync(s => s.SlotId == id); if (slot == null) return this.NotFound(); RatedLevel? ratedLevel = await this.database.RatedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId); VisitedLevel? visitedLevel = await this.database.VisitedLevels.FirstOrDefaultAsync(r => r.SlotId == id && r.UserId == user.UserId); - return this.Ok(slot.Serialize(gameVersion, ratedLevel, visitedLevel)); + Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == id && r.ReviewerId == user.UserId); + return this.Ok(slot.Serialize(gameVersion, ratedLevel, visitedLevel, review)); } [HttpGet("slots/cool")] @@ -129,7 +119,11 @@ public class SlotsController : ControllerBase GameVersion gameVersion = token.GameVersion; - IQueryable slots = this.getSlots(gameVersion).OrderByDescending(s => s.FirstUploaded).Skip(pageStart - 1).Take(Math.Min(pageSize, 30)); + IQueryable slots = this.database.Slots.ByGameVersion + (gameVersion) + .OrderByDescending(s => s.FirstUploaded) + .Skip(pageStart - 1) + .Take(Math.Min(pageSize, 30)); string response = Enumerable.Aggregate(slots, string.Empty, (current, slot) => current + slot.Serialize(gameVersion)); @@ -160,7 +154,7 @@ public class SlotsController : ControllerBase GameVersion gameVersion = token.GameVersion; - IQueryable slots = this.getSlots(gameVersion) + IQueryable slots = this.database.Slots.ByGameVersion(gameVersion) .Where(s => s.TeamPick) .OrderByDescending(s => s.LastUpdated) .Skip(pageStart - 1) @@ -194,7 +188,7 @@ public class SlotsController : ControllerBase GameVersion gameVersion = token.GameVersion; - IEnumerable slots = this.getSlots(gameVersion).OrderBy(_ => EF.Functions.Random()).Take(Math.Min(pageSize, 30)); + IEnumerable slots = this.database.Slots.ByGameVersion(gameVersion).OrderBy(_ => EF.Functions.Random()).Take(Math.Min(pageSize, 30)); string response = slots.Aggregate(string.Empty, (current, slot) => current + slot.Serialize(gameVersion)); @@ -383,7 +377,7 @@ public class SlotsController : ControllerBase { if (version == GameVersion.LittleBigPlanetVita || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown) { - return this.getSlots(version); + return this.database.Slots.ByGameVersion(version); } string _dateFilterType = dateFilterType ?? ""; diff --git a/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs b/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs index 3f84078f..ad99952e 100644 --- a/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs +++ b/ProjectLighthouse/Controllers/Website/Admin/AdminSlotController.cs @@ -62,10 +62,7 @@ public class AdminSlotController : ControllerBase if (slot.Location == null) throw new ArgumentNullException(); - this.database.Locations.Remove(slot.Location); - this.database.Slots.Remove(slot); - - await this.database.SaveChangesAsync(); + await this.database.RemoveSlot(slot); return this.Ok(); } diff --git a/ProjectLighthouse/Controllers/Website/Debug/RoomVisualizerController.cs b/ProjectLighthouse/Controllers/Website/Debug/RoomVisualizerController.cs index 854f05c9..cc690c93 100644 --- a/ProjectLighthouse/Controllers/Website/Debug/RoomVisualizerController.cs +++ b/ProjectLighthouse/Controllers/Website/Debug/RoomVisualizerController.cs @@ -26,7 +26,7 @@ public class RoomVisualizerController : ControllerBase return this.NotFound(); #else List users = await this.database.Users.OrderByDescending(_ => EF.Functions.Random()).Take(2).ToListAsync(); - RoomHelper.CreateRoom(users, GameVersion.LittleBigPlanet2); + RoomHelper.CreateRoom(users, GameVersion.LittleBigPlanet2, Platform.PS3); foreach (User user in users) { diff --git a/ProjectLighthouse/Controllers/Website/SlotPageController.cs b/ProjectLighthouse/Controllers/Website/SlotPageController.cs index 92edbb3e..24968595 100644 --- a/ProjectLighthouse/Controllers/Website/SlotPageController.cs +++ b/ProjectLighthouse/Controllers/Website/SlotPageController.cs @@ -1,5 +1,7 @@ #nullable enable using System.Threading.Tasks; +using Kettu; +using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Levels; using Microsoft.AspNetCore.Mvc; @@ -34,15 +36,20 @@ public class SlotPageController : ControllerBase return this.Redirect($"~/slot/{id}#{commentId}"); } - [HttpGet("postComment")] - public async Task PostComment([FromRoute] int id, [FromQuery] string? msg) + [HttpPost("postComment")] + public async Task PostComment([FromRoute] int id, [FromForm] string? msg) { User? user = this.database.UserFromWebRequest(this.Request); if (user == null) return this.Redirect("~/login"); - if (msg == null) return this.Redirect("~/slot/" + id); + if (msg == null) + { + Logger.Log($"Refusing to post comment from {user.UserId} on user {id}, {nameof(msg)} is null", LoggerLevelComments.Instance); + return this.Redirect("~/slot/" + id); + } await this.database.PostComment(user, id, CommentType.Level, msg); + Logger.Log($"Posted comment from {user.UserId}: \"{msg}\" on user {id}", LoggerLevelComments.Instance); return this.Redirect("~/slot/" + id); } diff --git a/ProjectLighthouse/Controllers/Website/UserPageController.cs b/ProjectLighthouse/Controllers/Website/UserPageController.cs index c22e1623..627ff511 100644 --- a/ProjectLighthouse/Controllers/Website/UserPageController.cs +++ b/ProjectLighthouse/Controllers/Website/UserPageController.cs @@ -1,10 +1,9 @@ #nullable enable -using System; using System.Threading.Tasks; +using Kettu; +using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Types; -using LBPUnion.ProjectLighthouse.Types.Profiles; using Microsoft.AspNetCore.Mvc; -using Microsoft.CodeAnalysis; using Microsoft.EntityFrameworkCore; namespace LBPUnion.ProjectLighthouse.Controllers.Website; @@ -20,20 +19,6 @@ public class UserPageController : ControllerBase this.database = database; } - [HttpGet("heart")] - public async Task HeartUser([FromRoute] int id) - { - User? user = this.database.UserFromWebRequest(this.Request); - if (user == null) return this.Redirect("~/login"); - - User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); - if (heartedUser == null) return this.NotFound(); - - await this.database.HeartUser(user, heartedUser); - - return this.Redirect("~/user/" + id); - } - [HttpGet("rateComment")] public async Task RateComment([FromRoute] int id, [FromQuery] int? commentId, [FromQuery] int? rating) { @@ -45,15 +30,34 @@ public class UserPageController : ControllerBase return this.Redirect($"~/user/{id}#{commentId}"); } - [HttpGet("postComment")] - public async Task PostComment([FromRoute] int id, [FromQuery] string? msg) + [HttpPost("postComment")] + public async Task PostComment([FromRoute] int id, [FromForm] string? msg) { User? user = this.database.UserFromWebRequest(this.Request); if (user == null) return this.Redirect("~/login"); - if (msg == null) return this.Redirect("~/user/" + id); + if (msg == null) + { + Logger.Log($"Refusing to post comment from {user.UserId} on user {id}, {nameof(msg)} is null", LoggerLevelComments.Instance); + return this.Redirect("~/user/" + id); + } await this.database.PostComment(user, id, CommentType.Profile, msg); + Logger.Log($"Posted comment from {user.UserId}: \"{msg}\" on user {id}", LoggerLevelComments.Instance); + + return this.Redirect("~/user/" + id); + } + + [HttpGet("heart")] + public async Task HeartUser([FromRoute] int id) + { + User? user = this.database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + User? heartedUser = await this.database.Users.FirstOrDefaultAsync(u => u.UserId == id); + if (heartedUser == null) return this.NotFound(); + + await this.database.HeartUser(user, heartedUser); return this.Redirect("~/user/" + id); } diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 6ae7f4e2..0ae12fde 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Types; @@ -46,7 +47,17 @@ public class Database : DbContext public async Task CreateUser(string username, string password) { - if (!password.StartsWith("$")) throw new ArgumentException(nameof(password) + " is not a BCrypt hash"); + if (!password.StartsWith('$')) throw new ArgumentException(nameof(password) + " is not a BCrypt hash"); + + // 16 is PSN max, 3 is PSN minimum + if (!ServerStatics.IsUnitTesting || !username.StartsWith("unitTestUser")) + { + if (username.Length > 16 || username.Length < 3) throw new ArgumentException(nameof(username) + " is either too long or too short"); + + Regex regex = new("^[a-zA-Z0-9_.-]*$"); + + if (!regex.IsMatch(username)) throw new ArgumentException(nameof(username) + " does not match the username regex"); + } User user; if ((user = await this.Users.Where(u => u.Username == username).FirstOrDefaultAsync()) != null) return user; @@ -82,6 +93,7 @@ public class Database : DbContext UserId = user.UserId, UserLocation = userLocation, GameVersion = npTicket.GameVersion, + Platform = npTicket.Platform, }; this.GameTokens.Add(gameToken); @@ -143,6 +155,8 @@ public class Database : DbContext public async Task PostComment(User user, int targetId, CommentType type, string message) { + if (message.Length > 100) return false; + if (type == CommentType.Profile) { User? targetUser = await this.Users.FirstOrDefaultAsync(u => u.UserId == targetId); @@ -151,7 +165,7 @@ public class Database : DbContext else { Slot? targetSlot = await this.Slots.FirstOrDefaultAsync(u => u.SlotId == targetId); - if(targetSlot == null) return false; + if (targetSlot == null) return false; } this.Comments.Add diff --git a/ProjectLighthouse/Helpers/Extensions/SlotsExtensions.cs b/ProjectLighthouse/Helpers/Extensions/SlotsExtensions.cs new file mode 100644 index 00000000..457ef413 --- /dev/null +++ b/ProjectLighthouse/Helpers/Extensions/SlotsExtensions.cs @@ -0,0 +1,31 @@ +using System.Linq; +using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Levels; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Helpers.Extensions; + +public static class SlotsExtensions +{ + public static IQueryable ByGameVersion + (this DbSet set, GameVersion gameVersion, bool includeSublevels = false) + => set.AsQueryable().ByGameVersion(gameVersion, includeSublevels); + + public static IQueryable ByGameVersion(this IQueryable queryable, GameVersion gameVersion, bool includeSublevels = false) + { + IQueryable query = queryable.Include(s => s.Creator).Include(s => s.Location); + + if (gameVersion == GameVersion.LittleBigPlanetVita || gameVersion == GameVersion.LittleBigPlanetPSP || gameVersion == GameVersion.Unknown) + { + query = query.Where(s => s.GameVersion == gameVersion); + } + else + { + query = query.Where(s => s.GameVersion <= gameVersion); + } + + if (!includeSublevels) query = query.Where(s => !s.SubLevel); + + return query; + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/FileHelper.cs b/ProjectLighthouse/Helpers/FileHelper.cs index 9d8c4f20..e3e39d17 100644 --- a/ProjectLighthouse/Helpers/FileHelper.cs +++ b/ProjectLighthouse/Helpers/FileHelper.cs @@ -1,7 +1,13 @@ +#nullable enable using System; +using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Kettu; +using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Types.Files; using LBPUnion.ProjectLighthouse.Types.Settings; @@ -101,4 +107,44 @@ public static class FileHelper } public static string[] ResourcesNotUploaded(params string[] hashes) => hashes.Where(hash => !ResourceExists(hash)).ToArray(); + + public static void ConvertAllTexturesToPng() + { + EnsureDirectoryCreated(Path.Combine(Environment.CurrentDirectory, "png")); + if (Directory.Exists("r")) + { + Logger.Log + ("Converting all textures to PNG. This may take a while if this is the first time running this operation...", LoggerLevelStartup.Instance); + + ConcurrentQueue fileQueue = new(); + + foreach (string filename in Directory.GetFiles("r")) fileQueue.Enqueue(filename); + + for(int i = 0; i < Environment.ProcessorCount; i++) + { + Task.Factory.StartNew + ( + () => + { + while (fileQueue.TryDequeue(out string? filename)) + { + LbpFile? file = LbpFile.FromHash(filename.Replace("r" + Path.DirectorySeparatorChar, "")); + if (file == null) continue; + + if (file.FileType == LbpFileType.Jpeg || file.FileType == LbpFileType.Png || file.FileType == LbpFileType.Texture) + { + ImageHelper.LbpFileToPNG(file); + } + } + } + ); + } + + while (!fileQueue.IsEmpty) + { + Thread.Sleep(100); + } + } + } + } \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/LastContactHelper.cs b/ProjectLighthouse/Helpers/LastContactHelper.cs index cdc7a06d..8f76bf22 100644 --- a/ProjectLighthouse/Helpers/LastContactHelper.cs +++ b/ProjectLighthouse/Helpers/LastContactHelper.cs @@ -11,7 +11,7 @@ public static class LastContactHelper { private static readonly Database database = new(); - public static async Task SetLastContact(User user, GameVersion gameVersion) + public static async Task SetLastContact(User user, GameVersion gameVersion, Platform platform) { LastContact? lastContact = await database.LastContacts.Where(l => l.UserId == user.UserId).FirstOrDefaultAsync(); @@ -28,6 +28,7 @@ public static class LastContactHelper lastContact.Timestamp = TimestampHelper.Timestamp; lastContact.GameVersion = gameVersion; + lastContact.Platform = platform; await database.SaveChangesAsync(); } diff --git a/ProjectLighthouse/Helpers/RoomHelper.cs b/ProjectLighthouse/Helpers/RoomHelper.cs index 7cca021b..81db373a 100644 --- a/ProjectLighthouse/Helpers/RoomHelper.cs +++ b/ProjectLighthouse/Helpers/RoomHelper.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading.Tasks; using Kettu; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Match; +using LBPUnion.ProjectLighthouse.Types.Profiles; namespace LBPUnion.ProjectLighthouse.Helpers; @@ -22,9 +24,25 @@ public class RoomHelper private static int roomIdIncrement; + public static void StartCleanupThread() + { + // ReSharper disable once FunctionNeverReturns + Task.Factory.StartNew + ( + async () => + { + while (true) + { + CleanupRooms(); + await Task.Delay(10000); + } + } + ); + } + internal static int RoomIdIncrement => roomIdIncrement++; - public static FindBestRoomResponse? FindBestRoom(User? user, GameVersion roomVersion, string? location) + public static FindBestRoomResponse? FindBestRoom(User? user, GameVersion roomVersion, Platform? platform, string? location) { if (roomVersion == GameVersion.LittleBigPlanet1 || roomVersion == GameVersion.LittleBigPlanetPSP) { @@ -42,6 +60,7 @@ public class RoomHelper } rooms = rooms.Where(r => r.RoomVersion == roomVersion).ToList(); + if (platform != null) rooms = rooms.Where(r => r.RoomPlatform == platform).ToList(); foreach (Room room in rooms) // Look for rooms looking for players before moving on to rooms that are idle. @@ -115,7 +134,7 @@ public class RoomHelper return null; } - public static Room CreateRoom(User user, GameVersion roomVersion, RoomSlot? slot = null) + public static Room CreateRoom(User user, GameVersion roomVersion, Platform roomPlatform, RoomSlot? slot = null) => CreateRoom ( new List @@ -123,9 +142,10 @@ public class RoomHelper user, }, roomVersion, + roomPlatform, slot ); - public static Room CreateRoom(List users, GameVersion roomVersion, RoomSlot? slot = null) + public static Room CreateRoom(List users, GameVersion roomVersion, Platform roomPlatform, RoomSlot? slot = null) { Room room = new() { @@ -134,6 +154,7 @@ public class RoomHelper State = RoomState.Idle, Slot = slot ?? PodSlot, RoomVersion = roomVersion, + RoomPlatform = roomPlatform, }; CleanupRooms(room.Host, room); @@ -143,13 +164,22 @@ public class RoomHelper return room; } - public static Room? FindRoomByUser(User user, GameVersion roomVersion, bool createIfDoesNotExist = false) + public static Room? FindRoomByUser(User user, GameVersion roomVersion, Platform roomPlatform, bool createIfDoesNotExist = false) { lock(Rooms) foreach (Room room in Rooms.Where(room => room.Players.Any(player => user == player))) return room; - return createIfDoesNotExist ? CreateRoom(user, roomVersion) : null; + return createIfDoesNotExist ? CreateRoom(user, roomVersion, roomPlatform) : null; + } + + public static Room? FindRoomByUserId(int userId) + { + lock(Rooms) + foreach (Room room in Rooms.Where(room => room.Players.Any(player => player.UserId == userId))) + return room; + + return null; } [SuppressMessage("ReSharper", "InvertIf")] @@ -157,6 +187,16 @@ public class RoomHelper { lock(Rooms) { + int roomCountBeforeCleanup = Rooms.Count; + + // Remove offline players from rooms + foreach (Room room in Rooms) + { + // do not shorten, this prevents collection modified errors + List playersToRemove = room.Players.Where(player => player.Status.StatusType == StatusType.Offline).ToList(); + foreach (User user in playersToRemove) room.Players.Remove(user); + } + // Delete old rooms based on host if (host != null) try @@ -179,6 +219,13 @@ public class RoomHelper Rooms.RemoveAll(r => r.Players.Count == 0); // Remove empty rooms Rooms.RemoveAll(r => r.Players.Count > 4); // Remove obviously bogus rooms + + int roomCountAfterCleanup = Rooms.Count; + + if (roomCountBeforeCleanup != roomCountAfterCleanup) + { + Logger.Log($"Cleaned up {roomCountBeforeCleanup - roomCountAfterCleanup} rooms.", LoggerLevelMatch.Instance); + } } } } \ No newline at end of file diff --git a/ProjectLighthouse/Logging/LoggerLevels.cs b/ProjectLighthouse/Logging/LoggerLevels.cs index e7175fc3..303dda97 100644 --- a/ProjectLighthouse/Logging/LoggerLevels.cs +++ b/ProjectLighthouse/Logging/LoggerLevels.cs @@ -63,6 +63,12 @@ public class LoggerLevelInflux : LoggerLevel public override string Name => "Influx"; } +public class LoggerLevelComments : LoggerLevel +{ + public static readonly LoggerLevelComments Instance = new(); + public override string Name => "Comments"; +} + public class LoggerLevelAspNet : LoggerLevel { diff --git a/ProjectLighthouse/Migrations/20220217045519_AddPlatformForLastContactsAndGameTokens.cs b/ProjectLighthouse/Migrations/20220217045519_AddPlatformForLastContactsAndGameTokens.cs new file mode 100644 index 00000000..78342cfe --- /dev/null +++ b/ProjectLighthouse/Migrations/20220217045519_AddPlatformForLastContactsAndGameTokens.cs @@ -0,0 +1,41 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20220217045519_AddPlatformForLastContactsAndGameTokens")] + public partial class AddPlatformForLastContactsAndGameTokens : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Platform", + table: "LastContacts", + type: "int", + nullable: false, + defaultValue: -1); + + migrationBuilder.AddColumn( + name: "Platform", + table: "GameTokens", + type: "int", + nullable: false, + defaultValue: -1); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Platform", + table: "LastContacts"); + + migrationBuilder.DropColumn( + name: "Platform", + table: "GameTokens"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index 21676d01..23e9069d 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -81,6 +81,9 @@ namespace ProjectLighthouse.Migrations b.Property("GameVersion") .HasColumnType("int"); + b.Property("Platform") + .HasColumnType("int"); + b.Property("Used") .HasColumnType("tinyint(1)"); @@ -458,6 +461,9 @@ namespace ProjectLighthouse.Migrations b.Property("GameVersion") .HasColumnType("int"); + b.Property("Platform") + .HasColumnType("int"); + b.Property("Timestamp") .HasColumnType("bigint"); diff --git a/ProjectLighthouse/Pages/Debug/RoomVisualizerPage.cshtml b/ProjectLighthouse/Pages/Debug/RoomVisualizerPage.cshtml index 0316b86c..265b4951 100644 --- a/ProjectLighthouse/Pages/Debug/RoomVisualizerPage.cshtml +++ b/ProjectLighthouse/Pages/Debug/RoomVisualizerPage.cshtml @@ -52,7 +52,7 @@ { if (version == GameVersion.LittleBigPlanet1 || version == GameVersion.LittleBigPlanetPSP || version == GameVersion.Unknown) continue; - FindBestRoomResponse? response = RoomHelper.FindBestRoom(null, version, null); + FindBestRoomResponse? response = RoomHelper.FindBestRoom(null, version, null, null); string text = response == null ? "No room found." : "Room " + response.RoomId;

Best room for @version.ToPrettyString(): @text

@@ -72,7 +72,7 @@ You are currently in this room.

} -

@room.Players.Count players, state is @room.State, version is @room.RoomVersion.ToPrettyString()

+

@room.Players.Count players, state is @room.State, version is @room.RoomVersion.ToPrettyString()on paltform @room.RoomPlatform

Slot type: @room.Slot.SlotType, slot id: @room.Slot.SlotId

@foreach (User player in room.Players) { diff --git a/ProjectLighthouse/Pages/Partials/CommentsPartial.cshtml b/ProjectLighthouse/Pages/Partials/CommentsPartial.cshtml index 7d038558..c6114822 100644 --- a/ProjectLighthouse/Pages/Partials/CommentsPartial.cshtml +++ b/ProjectLighthouse/Pages/Partials/CommentsPartial.cshtml @@ -1,7 +1,6 @@ @using System.IO @using System.Web @using LBPUnion.ProjectLighthouse.Types.Profiles -