diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/FriendsController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/FriendsController.cs index 9a06ca02..b86d6a79 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/FriendsController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/FriendsController.cs @@ -4,9 +4,7 @@ using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.PlayerData; using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.Serialization; -using LBPUnion.ProjectLighthouse.StorableLists; using LBPUnion.ProjectLighthouse.StorableLists.Stores; -using LBPUnion.ProjectLighthouse.Types; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -56,8 +54,7 @@ public class FriendsController : ControllerBase blockedUsers.Add(blockedUser.UserId); } - UserFriendData? friendStore = UserFriendStore.GetUserFriendData(token.UserId); - if (friendStore == null) friendStore = UserFriendStore.CreateUserFriendData(token.UserId); + UserFriendData friendStore = UserFriendStore.GetUserFriendData(token.UserId) ?? UserFriendStore.CreateUserFriendData(token.UserId); friendStore.FriendIds = friends.Select(u => u.UserId).ToList(); friendStore.BlockedIds = blockedUsers; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs index 549f18aa..1dff824d 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs @@ -3,9 +3,13 @@ using System.Diagnostics.CodeAnalysis; using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Helpers; using LBPUnion.ProjectLighthouse.Levels; +using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.PlayerData; +using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.Serialization; +using LBPUnion.ProjectLighthouse.StorableLists.Stores; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers.Slots; @@ -29,20 +33,45 @@ public class ScoreController : ControllerBase string username = await this.database.UsernameFromGameToken(token); - if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); + if (SlotHelper.IsTypeInvalid(slotType)) + { + Logger.Warn($"Rejecting score upload, slot type is invalid (slotType={slotType}, user={username})", LogArea.Score); + return this.BadRequest(); + } this.Request.Body.Position = 0; string bodyString = await new StreamReader(this.Request.Body).ReadToEndAsync(); XmlSerializer serializer = new(typeof(Score)); Score? score = (Score?)serializer.Deserialize(new StringReader(bodyString)); - if (score == null) return this.BadRequest(); + if (score == null) + { + Logger.Warn($"Rejecting score upload, score is null (slotType={slotType}, slotId={id}, user={username})", LogArea.Score); + return this.BadRequest(); + } - if (score.PlayerIds.Length == 0) return this.BadRequest(); + if (score.PlayerIds.Length == 0) + { + Logger.Warn($"Rejecting score upload, there are 0 playerIds (slotType={slotType}, slotId={id}, user={username})", LogArea.Score); + return this.BadRequest(); + } - if (score.Points < 0) return this.BadRequest(); + if (score.Points < 0) + { + Logger.Warn($"Rejecting score upload, points value is less than 0 (points={score.Points}, user={username})", LogArea.Score); + return this.BadRequest(); + } - if (score.Type is > 4 or < 1) return this.BadRequest(); + // Score types: + // 1-4: Co-op with the number representing the number of players + // 5: leaderboard filtered by day (never uploaded with this id) + // 6: leaderboard filtered by week (never uploaded either) + // 7: Versus levels & leaderboard filtered by all time + if (score.Type is > 4 or < 1 && score.Type != 7) + { + Logger.Warn($"Rejecting score upload, score type is out of bounds (type={score.Type}, user={username})", LogArea.Score); + return this.BadRequest(); + } SanitizationHelper.SanitizeStringsInClass(score); @@ -51,7 +80,11 @@ public class ScoreController : ControllerBase score.SlotId = id; Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId); - if (slot == null) return this.BadRequest(); + if (slot == null) + { + Logger.Warn($"Rejecting score upload, slot is null (slotId={score.SlotId}, slotType={slotType}, reqId={id}, user={username})", LogArea.Score); + return this.BadRequest(); + } switch (token.GameVersion) { @@ -109,10 +142,35 @@ public class ScoreController : ControllerBase return this.Ok(myRanking); } - [HttpGet("friendscores/user/{slotId:int}/{type:int}")] - public IActionResult FriendScores(int slotId, int type) - //=> await TopScores(slotId, type); - => this.Ok(LbpSerializer.BlankElement("scores")); + [HttpGet("friendscores/{slotType}/{slotId:int}/{type:int}")] + public async Task FriendScores(string slotType, int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5) + { + GameToken? token = await this.database.GameTokenFromRequest(this.Request); + if (token == null) return this.StatusCode(403, ""); + + if (pageSize <= 0) return this.BadRequest(); + + string username = await this.database.UsernameFromGameToken(token); + + if (SlotHelper.IsTypeInvalid(slotType)) return this.BadRequest(); + + if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer); + + UserFriendData? store = UserFriendStore.GetUserFriendData(token.UserId); + if (store == null) return this.Ok(); + + List friendNames = new(); + + foreach (int friendId in store.FriendIds) + { + string? friendUsername = await this.database.Users.Where(u => u.UserId == friendId) + .Select(u => u.Username) + .FirstOrDefaultAsync(); + if (friendUsername != null) friendNames.Add(friendUsername); + } + + return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize, "scores", friendNames.ToArray())); + } [HttpGet("topscores/{slotType}/{slotId:int}/{type:int}")] [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] @@ -140,13 +198,15 @@ public class ScoreController : ControllerBase string username, int pageStart = -1, int pageSize = 5, - string rootName = "scores" + string rootName = "scores", + string[]? playerIds = null ) { // This is hella ugly but it technically assigns the proper rank to a score // var needed for Anonymous type returned from SELECT var rankedScores = this.database.Scores + .Where(s => playerIds == null || playerIds.Any(id => s.PlayerIdCollection.Split(",", StringSplitOptions.None).Contains(id))) .Where(s => s.SlotId == slotId && s.Type == type) .OrderByDescending(s => s.Points) .ThenBy(s => s.ScoreId) diff --git a/ProjectLighthouse/Administration/Maintenance/RepeatingTasks/PerformCaseActionsTask.cs b/ProjectLighthouse/Administration/Maintenance/RepeatingTasks/PerformCaseActionsTask.cs index ba17e54c..688b84da 100644 --- a/ProjectLighthouse/Administration/Maintenance/RepeatingTasks/PerformCaseActionsTask.cs +++ b/ProjectLighthouse/Administration/Maintenance/RepeatingTasks/PerformCaseActionsTask.cs @@ -3,6 +3,7 @@ using System; using System.Linq; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Levels; +using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using Microsoft.EntityFrameworkCore; @@ -23,10 +24,23 @@ public class PerformCaseActionsTask : IRepeatingTask if (@case.Type.AffectsUser()) { user = await @case.GetUserAsync(database); + if (user == null) + { + Logger.Error($"Target user for case {@case.CaseId} is null (userId={@case.AffectedId}", LogArea.Maintenance); + @case.Processed = true; + continue; + } } else if(@case.Type.AffectsLevel()) { slot = await @case.GetSlotAsync(database); + if (slot == null) + { + Logger.Error($"Target slot for case {@case.CaseId} is null (slotId={@case.AffectedId}", LogArea.Maintenance); + // Just mark as processed, this needs to be handled better in the future + @case.Processed = true; + continue; + } } if (@case.Expired || @case.Dismissed) @@ -50,7 +64,6 @@ public class PerformCaseActionsTask : IRepeatingTask { slot!.Hidden = false; slot.HiddenReason = ""; - break; } case CaseType.LevelDisableComments: diff --git a/ProjectLighthouse/Administration/RepeatingTaskHandler.cs b/ProjectLighthouse/Administration/RepeatingTaskHandler.cs index af1388df..3885bd09 100644 --- a/ProjectLighthouse/Administration/RepeatingTaskHandler.cs +++ b/ProjectLighthouse/Administration/RepeatingTaskHandler.cs @@ -47,7 +47,7 @@ public static class RepeatingTaskHandler await task.Run(database); task.LastRan = DateTime.Now; - Logger.Debug($"Ran task \"{task.Name}\"", LogArea.Maintenace); + Logger.Debug($"Ran task \"{task.Name}\"", LogArea.Maintenance); } taskQueue.Enqueue(task); @@ -55,7 +55,7 @@ public static class RepeatingTaskHandler } catch(Exception e) { - Logger.Warn($"Error occured while processing repeating tasks: \n{e}", LogArea.Maintenace); + Logger.Warn($"Error occured while processing repeating tasks: \n{e}", LogArea.Maintenance); } } } diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 64ff8ce9..86d1a38a 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -451,7 +451,7 @@ public class Database : DbContext public async Task RemoveExpiredTokens() { - foreach (GameToken token in this.GameTokens.Where(t => DateTime.Now > t.ExpiresAt).ToList()) + foreach (GameToken token in await this.GameTokens.Where(t => DateTime.Now > t.ExpiresAt).ToListAsync()) { User? user = await this.Users.FirstOrDefaultAsync(u => u.UserId == token.UserId); if(user != null) user.LastLogout = TimeHelper.TimestampMillis; diff --git a/ProjectLighthouse/Logging/LogArea.cs b/ProjectLighthouse/Logging/LogArea.cs index 0612af9f..c80b003e 100644 --- a/ProjectLighthouse/Logging/LogArea.cs +++ b/ProjectLighthouse/Logging/LogArea.cs @@ -22,5 +22,6 @@ public enum LogArea Command, Admin, Publish, - Maintenace, + Maintenance, + Score, } \ No newline at end of file