diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/ActivityController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/ActivityController.cs new file mode 100644 index 00000000..f78fda68 --- /dev/null +++ b/ProjectLighthouse.Servers.GameServer/Controllers/ActivityController.cs @@ -0,0 +1,286 @@ +using System.Linq.Expressions; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Extensions; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.StorableLists.Stores; +using LBPUnion.ProjectLighthouse.Types.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using LBPUnion.ProjectLighthouse.Types.Entities.Token; +using LBPUnion.ProjectLighthouse.Types.Levels; +using LBPUnion.ProjectLighthouse.Types.Serialization.Activity; +using LBPUnion.ProjectLighthouse.Types.Users; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; + +[ApiController] +[Authorize] +[Route("LITTLEBIGPLANETPS3_XML/stream")] +[Produces("text/xml")] +public class ActivityController : ControllerBase +{ + private readonly DatabaseContext database; + + public ActivityController(DatabaseContext database) + { + this.database = database; + } + + public class ActivityDto + { + public required ActivityEntity Activity { get; set; } + public int? TargetSlotId { get; set; } + public int? TargetUserId { get; set; } + public int? TargetPlaylistId { get; set; } + public int? SlotCreatorId { get; set; } + } + //TODO refactor this mess into a separate db file or something + + private static Expression> ActivityToDto() + { + return a => new ActivityDto + { + Activity = a, + TargetSlotId = a is LevelActivityEntity + ? ((LevelActivityEntity)a).SlotId + : a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.PhotoId != 0 + ? ((PhotoActivityEntity)a).Photo.SlotId + : a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Level + ? ((CommentActivityEntity)a).Comment.TargetId + : a is ScoreActivityEntity + ? ((ScoreActivityEntity)a).Score.SlotId + : 0, + + TargetUserId = a is UserActivityEntity + ? ((UserActivityEntity)a).TargetUserId + : a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Profile + ? ((CommentActivityEntity)a).Comment.TargetId + : a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.SlotId != 0 + ? ((PhotoActivityEntity)a).Photo.CreatorId + : 0, + TargetPlaylistId = a is PlaylistActivityEntity ? ((PlaylistActivityEntity)a).PlaylistId : 0, + }; + } + + private static IQueryable> GroupActivities + (IQueryable activityQuery) + { + return activityQuery.Select(ActivityToDto()) + .GroupBy(dto => new ActivityGroup + { + Timestamp = dto.Activity.Timestamp.Date, + UserId = dto.Activity.UserId, + TargetUserId = dto.TargetUserId, + TargetSlotId = dto.TargetSlotId, + TargetPlaylistId = dto.TargetPlaylistId, + }, + dto => dto.Activity); + } + + private static IQueryable> GroupActivities + (IQueryable activityQuery) + { + return activityQuery.GroupBy(dto => new ActivityGroup + { + Timestamp = dto.Activity.Timestamp.Date, + UserId = dto.Activity.UserId, + TargetUserId = dto.TargetUserId, + TargetSlotId = dto.TargetSlotId, + TargetPlaylistId = dto.TargetPlaylistId, + }, + dto => dto.Activity); + } + + // TODO this is kinda ass, can maybe improve once comment migration is merged + private async Task> GetFilters + ( + GameTokenEntity token, + bool excludeNews, + bool excludeMyLevels, + bool excludeFriends, + bool excludeFavouriteUsers, + bool excludeMyself + ) + { + IQueryable query = this.database.Activities.AsQueryable(); + if (excludeNews) query = query.Where(a => a.Type != EventType.NewsPost); + + IQueryable dtoQuery = query.Select(a => new ActivityDto + { + Activity = a, + SlotCreatorId = a is LevelActivityEntity + ? ((LevelActivityEntity)a).Slot.CreatorId + : a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.SlotId != 0 + ? ((PhotoActivityEntity)a).Photo.Slot!.CreatorId + : a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Level + ? ((CommentActivityEntity)a).Comment.TargetId + : a is ScoreActivityEntity + ? ((ScoreActivityEntity)a).Score.Slot.CreatorId + : 0, + }); + + Expression> predicate = PredicateExtensions.False(); + + predicate = predicate.Or(a => a.SlotCreatorId == 0 || excludeMyLevels + ? a.SlotCreatorId != token.UserId + : a.SlotCreatorId == token.UserId); + + List? friendIds = UserFriendStore.GetUserFriendData(token.UserId)?.FriendIds; + if (friendIds != null) + { + predicate = excludeFriends + ? predicate.Or(a => !friendIds.Contains(a.Activity.UserId)) + : predicate.Or(a => friendIds.Contains(a.Activity.UserId)); + } + + List favouriteUsers = await this.database.HeartedProfiles.Where(hp => hp.UserId == token.UserId) + .Select(hp => hp.HeartedUserId) + .ToListAsync(); + + predicate = excludeFavouriteUsers + ? predicate.Or(a => !favouriteUsers.Contains(a.Activity.UserId)) + : predicate.Or(a => favouriteUsers.Contains(a.Activity.UserId)); + + predicate = excludeMyself + ? predicate.Or(a => a.Activity.UserId != token.UserId) + : predicate.Or(a => a.Activity.UserId == token.UserId); + + query = dtoQuery.Where(predicate).Select(dto => dto.Activity); + + return query.OrderByDescending(a => a.Timestamp); + } + + public Task GetMostRecentEventTime(GameTokenEntity token, DateTime upperBound) + { + return this.database.Activities.Where(a => a.UserId == token.UserId) + .Where(a => a.Timestamp < upperBound) + .OrderByDescending(a => a.Timestamp) + .Select(a => a.Timestamp) + .FirstOrDefaultAsync(); + } + + [HttpGet] + public async Task GlobalActivity + ( + long timestamp, + bool excludeNews, + bool excludeMyLevels, + bool excludeFriends, + bool excludeFavouriteUsers, + bool excludeMyself + ) + { + GameTokenEntity token = this.GetToken(); + + if (token.GameVersion == GameVersion.LittleBigPlanet1) return this.BadRequest(); + + if (timestamp > TimeHelper.TimestampMillis || timestamp <= 0) timestamp = TimeHelper.TimestampMillis; + + DateTime start = DateTimeExtensions.FromUnixTimeMilliseconds(timestamp); + + DateTime soonestTime = await this.GetMostRecentEventTime(token, start); + Console.WriteLine(@"Most recent event occurred at " + soonestTime); + soonestTime = soonestTime.Subtract(TimeSpan.FromDays(1)); + + long soonestTimestamp = soonestTime.ToUnixTimeMilliseconds(); + + long endTimestamp = soonestTimestamp - 86_400_000; + + Console.WriteLine(@$"soonestTime: {soonestTimestamp}, endTime: {endTimestamp}"); + + IQueryable activityEvents = await this.GetFilters(token, + excludeNews, + excludeMyLevels, + excludeFriends, + excludeFavouriteUsers, + excludeMyself); + + DateTime end = DateTimeExtensions.FromUnixTimeMilliseconds(endTimestamp); + + activityEvents = activityEvents.Where(a => a.Timestamp < start && a.Timestamp > end); + + Console.WriteLine($@"start: {start}, end: {end}"); + + List> groups = await GroupActivities(activityEvents).ToListAsync(); + + foreach (IGrouping group in groups) + { + ActivityGroup key = group.Key; + Console.WriteLine( + $@"{key.GroupType}: Timestamp: {key.Timestamp}, UserId: {key.UserId}, TargetSlotId: {key.TargetSlotId}, " + + @$"TargetUserId: {key.TargetUserId}, TargetPlaylistId: {key.TargetPlaylistId}"); + foreach (ActivityEntity activity in group) + { + Console.WriteLine($@" {activity.Type}: Timestamp: {activity.Timestamp}"); + } + } + + DateTime oldestTime = groups.Any() ? groups.Min(g => g.Any() ? g.Min(a => a.Timestamp) : end) : end; + long oldestTimestamp = oldestTime.ToUnixTimeMilliseconds(); + + return this.Ok(await GameStream.CreateFromEntityResult(this.database, token, groups, timestamp, oldestTimestamp)); + } + + [HttpGet("slot/{slotType}/{slotId:int}")] + public async Task SlotActivity(string slotType, int slotId, long timestamp) + { + GameTokenEntity token = this.GetToken(); + + if (token.GameVersion == GameVersion.LittleBigPlanet1) return this.BadRequest(); + + if (timestamp > TimeHelper.TimestampMillis || timestamp <= 0) timestamp = TimeHelper.TimestampMillis; + + long endTimestamp = timestamp - 864_000; + + if (slotType is not ("developer" or "user")) return this.BadRequest(); + + if (slotType == "developer") + slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer); + + IQueryable slotActivity = this.database.Activities.Select(ActivityToDto()) + .Where(a => a.TargetSlotId == slotId); + + DateTime start = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime; + DateTime end = DateTimeOffset.FromUnixTimeMilliseconds(endTimestamp).DateTime; + + slotActivity = slotActivity.Where(a => a.Activity.Timestamp < start && a.Activity.Timestamp > end); + + List> groups = await GroupActivities(slotActivity).ToListAsync(); + + DateTime oldestTime = groups.Max(g => g.Max(a => a.Timestamp)); + long oldestTimestamp = new DateTimeOffset(oldestTime).ToUnixTimeMilliseconds(); + + return this.Ok(await GameStream.CreateFromEntityResult(this.database, token, groups, timestamp, oldestTimestamp)); + } + + [HttpGet("user2/{userId:int}/")] + public async Task UserActivity(int userId, long timestamp) + { + GameTokenEntity token = this.GetToken(); + + if (token.GameVersion == GameVersion.LittleBigPlanet1) return this.BadRequest(); + + if (timestamp > TimeHelper.TimestampMillis || timestamp <= 0) timestamp = TimeHelper.TimestampMillis; + + long endTimestamp = timestamp - 864_000; + + IQueryable userActivity = this.database.Activities.Select(ActivityToDto()) + .Where(a => a.TargetUserId == userId); + + DateTime start = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime; + DateTime end = DateTimeOffset.FromUnixTimeMilliseconds(endTimestamp).DateTime; + + userActivity = userActivity.Where(a => a.Activity.Timestamp < start && a.Activity.Timestamp > end); + + List> groups = await GroupActivities(userActivity).ToListAsync(); + + DateTime oldestTime = groups.Max(g => g.Max(a => a.Timestamp)); + long oldestTimestamp = new DateTimeOffset(oldestTime).ToUnixTimeMilliseconds(); + + return this.Ok( + await GameStream.CreateFromEntityResult(this.database, token, groups, timestamp, oldestTimestamp)); + } +} \ No newline at end of file diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs index 76f2b715..ae170499 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs @@ -9,7 +9,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Filter; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Logging; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Comment; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -44,6 +44,20 @@ public class CommentController : ControllerBase return this.Ok(); } + [HttpGet("userComment/{username}")] + [HttpGet("comment/{slotType}/{slotId:int}")] + public async Task GetSingleComment(string? username, string? slotType, int? slotId, int commentId) + { + GameTokenEntity token = this.GetToken(); + + if (username == null == (SlotHelper.IsTypeInvalid(slotType) || slotId == null)) return this.BadRequest(); + + CommentEntity? comment = await this.database.Comments.FindAsync(commentId); + if (comment == null) return this.NotFound(); + + return this.Ok(GameComment.CreateFromEntity(comment, token.UserId)); + } + [HttpGet("comments/{slotType}/{slotId:int}")] [HttpGet("userComments/{username}")] public async Task GetComments(string? username, string? slotType, int slotId) diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/FriendsController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/FriendsController.cs index 83e198d8..a0381f46 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/FriendsController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/FriendsController.cs @@ -5,7 +5,7 @@ using LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Users; using LBPUnion.ProjectLighthouse.StorableLists.Stores; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.User; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs index e7962643..07a80775 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Resources/PhotosController.cs @@ -12,7 +12,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Filter; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Logging; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Photo; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/CategoryController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/CategoryController.cs index 60ac1c58..b9a36852 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/CategoryController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/CategoryController.cs @@ -13,6 +13,9 @@ using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Logging; using LBPUnion.ProjectLighthouse.Types.Misc; using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Playlist; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; +using LBPUnion.ProjectLighthouse.Types.Serialization.User; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs index 67fff180..3db2016b 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ListController.cs @@ -9,7 +9,9 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Filter; using LBPUnion.ProjectLighthouse.Types.Levels; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Playlist; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; +using LBPUnion.ProjectLighthouse.Types.Serialization.User; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PlaylistController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PlaylistController.cs index 4763708a..46e8b90e 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PlaylistController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PlaylistController.cs @@ -4,7 +4,8 @@ using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Token; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Playlist; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs index e09a06e6..9a013a9e 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/PublishController.cs @@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Logging; using LBPUnion.ProjectLighthouse.Types.Resources; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs index aa4e1c5b..c32df338 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ReviewController.cs @@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Interaction; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Filter; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs index 79ae3163..841cef38 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/ScoreController.cs @@ -8,7 +8,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Logging; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Score; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SearchController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SearchController.cs index a4f129b5..064af5ac 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SearchController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SearchController.cs @@ -8,7 +8,7 @@ using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Filter; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SlotsController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SlotsController.cs index 13f9afa8..d0930e0a 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SlotsController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/Slots/SlotsController.cs @@ -13,7 +13,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Filter; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Misc; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/StatisticsController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/StatisticsController.cs index 3eb828e4..d45c1ff0 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/StatisticsController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/StatisticsController.cs @@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Filter; using LBPUnion.ProjectLighthouse.Filter.Filters; using LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs index fb5b6128..8caa0e14 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/UserController.cs @@ -10,7 +10,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Logging; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.User; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/ProjectLighthouse.Servers.GameServer/Extensions/DatabaseContextExtensions.cs b/ProjectLighthouse.Servers.GameServer/Extensions/DatabaseContextExtensions.cs index bef6943e..ee09930b 100644 --- a/ProjectLighthouse.Servers.GameServer/Extensions/DatabaseContextExtensions.cs +++ b/ProjectLighthouse.Servers.GameServer/Extensions/DatabaseContextExtensions.cs @@ -7,7 +7,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Filter; using LBPUnion.ProjectLighthouse.Types.Filter.Sorts; using LBPUnion.ProjectLighthouse.Types.Misc; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; using Microsoft.EntityFrameworkCore; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Extensions; diff --git a/ProjectLighthouse.Servers.GameServer/Types/Categories/PlaylistCategory.cs b/ProjectLighthouse.Servers.GameServer/Types/Categories/PlaylistCategory.cs index 00ed79ce..a7212306 100644 --- a/ProjectLighthouse.Servers.GameServer/Types/Categories/PlaylistCategory.cs +++ b/ProjectLighthouse.Servers.GameServer/Types/Categories/PlaylistCategory.cs @@ -5,6 +5,8 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Playlist; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; using Microsoft.EntityFrameworkCore; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories; diff --git a/ProjectLighthouse.Servers.GameServer/Types/Categories/SlotCategory.cs b/ProjectLighthouse.Servers.GameServer/Types/Categories/SlotCategory.cs index 39d6986d..5ed58a06 100644 --- a/ProjectLighthouse.Servers.GameServer/Types/Categories/SlotCategory.cs +++ b/ProjectLighthouse.Servers.GameServer/Types/Categories/SlotCategory.cs @@ -5,6 +5,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; using Microsoft.EntityFrameworkCore; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories; diff --git a/ProjectLighthouse.Servers.GameServer/Types/Categories/UserCategory.cs b/ProjectLighthouse.Servers.GameServer/Types/Categories/UserCategory.cs index d9826a92..bd188d7a 100644 --- a/ProjectLighthouse.Servers.GameServer/Types/Categories/UserCategory.cs +++ b/ProjectLighthouse.Servers.GameServer/Types/Categories/UserCategory.cs @@ -5,6 +5,8 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; +using LBPUnion.ProjectLighthouse.Types.Serialization.User; using Microsoft.EntityFrameworkCore; namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories; diff --git a/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationRemovalController.cs b/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationRemovalController.cs index 782b3991..cb235655 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationRemovalController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationRemovalController.cs @@ -1,7 +1,7 @@ using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml index 70b98e64..66fea3d9 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/PhotoPartial.cshtml @@ -3,7 +3,7 @@ @using LBPUnion.ProjectLighthouse.Servers.Website.Extensions @using LBPUnion.ProjectLighthouse.Types.Entities.Profile @using LBPUnion.ProjectLighthouse.Types.Levels -@using LBPUnion.ProjectLighthouse.Types.Serialization +@using LBPUnion.ProjectLighthouse.Types.Serialization.Photo @model LBPUnion.ProjectLighthouse.Types.Entities.Profile.PhotoEntity @{ diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/ReviewPartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/ReviewPartial.cshtml index 61034265..18c3d060 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/ReviewPartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/ReviewPartial.cshtml @@ -3,7 +3,7 @@ @using LBPUnion.ProjectLighthouse.Files @using LBPUnion.ProjectLighthouse.Helpers @using LBPUnion.ProjectLighthouse.Types.Entities.Level -@using LBPUnion.ProjectLighthouse.Types.Serialization +@using LBPUnion.ProjectLighthouse.Types.Serialization.Review @{ bool isMobile = (bool?)ViewData["IsMobile"] ?? false; diff --git a/ProjectLighthouse.Tests.GameApiTests/Integration/SlotFilterTests.cs b/ProjectLighthouse.Tests.GameApiTests/Integration/SlotFilterTests.cs index eacce233..e448f2db 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Integration/SlotFilterTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Integration/SlotFilterTests.cs @@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Tests.Helpers; using LBPUnion.ProjectLighthouse.Tests.Integration; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; using LBPUnion.ProjectLighthouse.Types.Users; using Xunit; diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/SlotControllerTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/SlotControllerTests.cs index 7408fcf5..66e5f6d6 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/SlotControllerTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/SlotControllerTests.cs @@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Matchmaking.Rooms; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.AspNetCore.Mvc; using Xunit; diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/StatisticsControllerTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/StatisticsControllerTests.cs index 5335b2cf..42049a31 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/StatisticsControllerTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/StatisticsControllerTests.cs @@ -4,7 +4,7 @@ using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; using LBPUnion.ProjectLighthouse.Tests.Helpers; using LBPUnion.ProjectLighthouse.Types.Entities.Level; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.AspNetCore.Mvc; using Xunit; diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/UserControllerTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/UserControllerTests.cs index e7228b2c..8c76f0b0 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/UserControllerTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Controllers/UserControllerTests.cs @@ -5,7 +5,7 @@ using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Servers.GameServer.Controllers; using LBPUnion.ProjectLighthouse.Tests.Helpers; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.User; using Microsoft.AspNetCore.Mvc; using Xunit; diff --git a/ProjectLighthouse.Tests/Unit/LocationTests.cs b/ProjectLighthouse.Tests/Unit/LocationTests.cs index a97b80af..ec9c80ca 100644 --- a/ProjectLighthouse.Tests/Unit/LocationTests.cs +++ b/ProjectLighthouse.Tests/Unit/LocationTests.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Misc; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; using Xunit; namespace LBPUnion.ProjectLighthouse.Tests.Unit; diff --git a/ProjectLighthouse.Tests/Unit/PaginationTests.cs b/ProjectLighthouse.Tests/Unit/PaginationTests.cs index de591d94..ecf4eb9a 100644 --- a/ProjectLighthouse.Tests/Unit/PaginationTests.cs +++ b/ProjectLighthouse.Tests/Unit/PaginationTests.cs @@ -3,7 +3,7 @@ using System.Linq; using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Types.Filter; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.User; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Xunit; diff --git a/ProjectLighthouse/Database/ActivityInterceptor.cs b/ProjectLighthouse/Database/ActivityInterceptor.cs new file mode 100644 index 00000000..ca5d66f5 --- /dev/null +++ b/ProjectLighthouse/Database/ActivityInterceptor.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Types.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; + +namespace LBPUnion.ProjectLighthouse.Database; + +public class ActivityInterceptor : SaveChangesInterceptor +{ + private class CustomTrackedEntity + { + public required EntityState State { get; init; } + public required object Entity { get; init; } + public required object OldEntity { get; init; } + } + + private readonly ConcurrentDictionary<(Type Type, int HashCode), CustomTrackedEntity> unsavedEntities; + private readonly IEntityEventHandler eventHandler; + + public ActivityInterceptor(IEntityEventHandler eventHandler) + { + this.eventHandler = eventHandler; + this.unsavedEntities = new ConcurrentDictionary<(Type Type, int HashCode), CustomTrackedEntity>(); + } + + #region Hooking stuff + + public override InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result) + { + this.SaveNewEntities(eventData); + return base.SavingChanges(eventData, result); + } + + public override ValueTask> SavingChangesAsync + (DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = new()) + { + this.SaveNewEntities(eventData); + return base.SavingChangesAsync(eventData, result, cancellationToken); + } + + public override int SavedChanges(SaveChangesCompletedEventData eventData, int result) + { + this.ParseInsertedEntities(eventData); + return base.SavedChanges(eventData, result); + } + + public override ValueTask SavedChangesAsync + (SaveChangesCompletedEventData eventData, int result, CancellationToken cancellationToken = new()) + { + this.ParseInsertedEntities(eventData); + return base.SavedChangesAsync(eventData, result, cancellationToken); + } + + #endregion + + private void SaveNewEntities(DbContextEventData eventData) + { + if (eventData.Context == null) return; + + DbContext context = eventData.Context; + + this.unsavedEntities.Clear(); + + foreach (EntityEntry entry in context.ChangeTracker.Entries()) + { + // Ignore activities + if (entry.Metadata.BaseType?.ClrType == typeof(ActivityEntity) || entry.Metadata.ClrType == typeof(LastContactEntity)) continue; + + // Ignore tokens + if (entry.Metadata.Name.Contains("Token")) continue; + + if (entry.State is not (EntityState.Added or EntityState.Deleted or EntityState.Modified)) continue; + + this.unsavedEntities.TryAdd((entry.Entity.GetType(), entry.Entity.GetHashCode()), + new CustomTrackedEntity + { + State = entry.State, + Entity = entry.Entity, + OldEntity = entry.OriginalValues.ToObject(), + }); + } + } + + private void ParseInsertedEntities(DbContextEventData eventData) + { + if (eventData.Context is not DatabaseContext context) return; + + HashSet entities = new(); + + List entries = context.ChangeTracker.Entries().ToList(); + + foreach (KeyValuePair<(Type Type, int HashCode), CustomTrackedEntity> kvp in this.unsavedEntities) + { + EntityEntry entry = entries.FirstOrDefault(e => + e.Metadata.ClrType == kvp.Key.Type && e.Entity.GetHashCode() == kvp.Key.HashCode); + switch (kvp.Value.State) + { + case EntityState.Added: + case EntityState.Modified: + if (entry != null) entities.Add(kvp.Value); + break; + case EntityState.Deleted: + if (entry == null) entities.Add(kvp.Value); + break; + case EntityState.Detached: + case EntityState.Unchanged: + default: + break; + } + } + + foreach (CustomTrackedEntity entity in entities) + { + switch (entity.State) + { + case EntityState.Added: + this.eventHandler.OnEntityInserted(context, entity.Entity); + break; + case EntityState.Deleted: + this.eventHandler.OnEntityDeleted(context, entity.Entity); + break; + case EntityState.Modified: + this.eventHandler.OnEntityChanged(context, entity.OldEntity, entity.Entity); + break; + case EntityState.Detached: + case EntityState.Unchanged: + default: + continue; + } + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Database/DatabaseContext.cs b/ProjectLighthouse/Database/DatabaseContext.cs index 699fc2f7..46394529 100644 --- a/ProjectLighthouse/Database/DatabaseContext.cs +++ b/ProjectLighthouse/Database/DatabaseContext.cs @@ -1,4 +1,5 @@ using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Types.Activity; using LBPUnion.ProjectLighthouse.Types.Entities.Activity; using LBPUnion.ProjectLighthouse.Types.Entities.Interaction; using LBPUnion.ProjectLighthouse.Types.Entities.Level; @@ -86,16 +87,6 @@ public partial class DatabaseContext : DbContext public DatabaseContext(DbContextOptions options) : base(options) { } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity().UseTpcMappingStrategy(); - modelBuilder.Entity().UseTpcMappingStrategy(); - modelBuilder.Entity().UseTpcMappingStrategy(); - modelBuilder.Entity().UseTpcMappingStrategy(); - modelBuilder.Entity().UseTpcMappingStrategy(); - base.OnModelCreating(modelBuilder); - } - public static DatabaseContext CreateNewInstance() { DbContextOptionsBuilder builder = new(); @@ -103,4 +94,26 @@ public partial class DatabaseContext : DbContext MySqlServerVersion.LatestSupportedServerVersion); return new DatabaseContext(builder.Options); } + + #region Activity + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + //TODO implement reviews + modelBuilder.Entity().UseTphMappingStrategy(); + modelBuilder.Entity().UseTphMappingStrategy(); + modelBuilder.Entity().UseTphMappingStrategy(); + modelBuilder.Entity().UseTphMappingStrategy(); + modelBuilder.Entity().UseTphMappingStrategy(); + modelBuilder.Entity().UseTphMappingStrategy(); + modelBuilder.Entity().UseTphMappingStrategy(); + modelBuilder.Entity().UseTphMappingStrategy(); + base.OnModelCreating(modelBuilder); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.AddInterceptors(new ActivityInterceptor(new ActivityEntityEventHandler())); + base.OnConfiguring(optionsBuilder); + } + #endregion } \ No newline at end of file diff --git a/ProjectLighthouse/Extensions/DateTimeExtensions.cs b/ProjectLighthouse/Extensions/DateTimeExtensions.cs new file mode 100644 index 00000000..6cb03759 --- /dev/null +++ b/ProjectLighthouse/Extensions/DateTimeExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace LBPUnion.ProjectLighthouse.Extensions; + +public static class DateTimeExtensions +{ + public static long ToUnixTimeMilliseconds(this DateTime dateTime) => + new DateTimeOffset(dateTime).ToUniversalTime().ToUnixTimeMilliseconds(); + + public static DateTime FromUnixTimeMilliseconds(long timestamp) => + DateTimeOffset.FromUnixTimeMilliseconds(timestamp).ToUniversalTime().DateTime; +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs b/ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs new file mode 100644 index 00000000..60caca08 --- /dev/null +++ b/ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs @@ -0,0 +1,179 @@ +#nullable enable +using System; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Reflection; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Types.Entities.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Interaction; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using LBPUnion.ProjectLighthouse.Types.Levels; + +namespace LBPUnion.ProjectLighthouse.Types.Activity; + +//TODO implement missing event triggers +public class ActivityEntityEventHandler : IEntityEventHandler +{ + public void OnEntityInserted(DatabaseContext database, T entity) where T : class + { + Console.WriteLine($@"OnEntityInserted: {entity.GetType().Name}"); + ActivityEntity? activity = entity switch + { + SlotEntity slot => new LevelActivityEntity + { + Type = EventType.PublishLevel, + SlotId = slot.SlotId, + UserId = slot.CreatorId, + }, + CommentEntity comment => new CommentActivityEntity + { + Type = comment.Type == CommentType.Level ? EventType.CommentOnLevel : EventType.CommentOnUser, + CommentId = comment.CommentId, + UserId = comment.PosterUserId, + }, + PhotoEntity photo => new PhotoActivityEntity + { + Type = EventType.UploadPhoto, + PhotoId = photo.PhotoId, + UserId = photo.CreatorId, + }, + ScoreEntity score => new ScoreActivityEntity + { + Type = EventType.Score, + ScoreId = score.ScoreId, + //TODO merge score migration + // UserId = int.Parse(score.PlayerIds[0]), + }, + HeartedLevelEntity heartedLevel => new LevelActivityEntity + { + Type = EventType.HeartLevel, + SlotId = heartedLevel.SlotId, + UserId = heartedLevel.UserId, + }, + HeartedProfileEntity heartedProfile => new UserActivityEntity + { + Type = EventType.HeartUser, + TargetUserId = heartedProfile.HeartedUserId, + UserId = heartedProfile.UserId, + }, + VisitedLevelEntity visitedLevel => new LevelActivityEntity + { + Type = EventType.PlayLevel, + SlotId = visitedLevel.SlotId, + UserId = visitedLevel.UserId, + }, + _ => null, + }; + InsertActivity(database, activity); + } + + private static void InsertActivity(DatabaseContext database, ActivityEntity? activity) + { + if (activity == null) return; + + Console.WriteLine("Inserting activity: " + activity.GetType().Name); + + activity.Timestamp = DateTime.UtcNow; + database.Activities.Add(activity); + database.SaveChanges(); + } + + public void OnEntityChanged(DatabaseContext database, T origEntity, T currentEntity) where T : class + { + foreach (PropertyInfo propInfo in currentEntity.GetType().GetProperties()) + { + if (!propInfo.CanRead || !propInfo.CanWrite) continue; + + if (propInfo.CustomAttributes.Any(c => c.AttributeType == typeof(NotMappedAttribute))) continue; + + object? origVal = propInfo.GetValue(origEntity); + object? newVal = propInfo.GetValue(currentEntity); + if ((origVal == null && newVal == null) || (origVal != null && newVal != null && origVal.Equals(newVal))) + continue; + + Console.WriteLine($@"Value for {propInfo.Name} changed"); + Console.WriteLine($@"Orig val: {origVal?.ToString() ?? "null"}"); + Console.WriteLine($@"New val: {newVal?.ToString() ?? "null"}"); + } + + Console.WriteLine($@"OnEntityChanged: {currentEntity.GetType().Name}"); + ActivityEntity? activity = null; + switch (currentEntity) + { + case VisitedLevelEntity visitedLevel: + { + if (origEntity is not VisitedLevelEntity) break; + + activity = new LevelActivityEntity + { + Type = EventType.PlayLevel, + SlotId = visitedLevel.SlotId, + UserId = visitedLevel.UserId, + }; + break; + } + case SlotEntity slotEntity: + { + if (origEntity is not SlotEntity oldSlotEntity) break; + + if (!oldSlotEntity.TeamPick && slotEntity.TeamPick) + { + activity = new LevelActivityEntity + { + Type = EventType.MMPickLevel, + SlotId = slotEntity.SlotId, + UserId = SlotHelper.GetPlaceholderUserId(database).Result, + }; + } + else if (oldSlotEntity.SlotId == slotEntity.SlotId && slotEntity.Type == SlotType.User) + { + activity = new LevelActivityEntity + { + Type = EventType.PublishLevel, + SlotId = slotEntity.SlotId, + UserId = slotEntity.CreatorId, + }; + } + + break; + } + } + + InsertActivity(database, activity); + } + + public void OnEntityDeleted(DatabaseContext database, T entity) where T : class + { + Console.WriteLine($@"OnEntityDeleted: {entity.GetType().Name}"); + ActivityEntity? activity = entity switch + { + //TODO move this to EntityModified and use CommentEntity.Deleted + CommentEntity comment => comment.Type switch + { + CommentType.Level => new CommentActivityEntity + { + Type = EventType.DeleteLevelComment, + CommentId = comment.CommentId, + UserId = comment.PosterUserId, + }, + _ => null, + }, + HeartedLevelEntity heartedLevel => new LevelActivityEntity + { + Type = EventType.UnheartLevel, + SlotId = heartedLevel.SlotId, + UserId = heartedLevel.UserId, + }, + HeartedProfileEntity heartedProfile => new UserActivityEntity + { + Type = EventType.UnheartUser, + TargetUserId = heartedProfile.HeartedUserId, + UserId = heartedProfile.UserId, + }, + _ => null, + }; + InsertActivity(database, activity); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Activity/ActivityGroup.cs b/ProjectLighthouse/Types/Activity/ActivityGroup.cs new file mode 100644 index 00000000..61ae381c --- /dev/null +++ b/ProjectLighthouse/Types/Activity/ActivityGroup.cs @@ -0,0 +1,41 @@ +using System; +using System.Xml.Serialization; + +namespace LBPUnion.ProjectLighthouse.Types.Activity; + +public class ActivityGroup +{ + public DateTime Timestamp { get; set; } + public int UserId { get; set; } + public int? TargetSlotId { get; set; } + public int? TargetUserId { get; set; } + public int? TargetPlaylistId { get; set; } + + public int TargetId => + this.GroupType switch + { + ActivityGroupType.User => this.TargetUserId ?? 0, + ActivityGroupType.Level => this.TargetSlotId ?? 0, + ActivityGroupType.Playlist => this.TargetPlaylistId ?? 0, + _ => this.UserId, + }; + + public ActivityGroupType GroupType => + this.TargetSlotId != 0 + ? ActivityGroupType.Level + : this.TargetUserId != 0 + ? ActivityGroupType.User + : ActivityGroupType.Playlist; +} + +public enum ActivityGroupType +{ + [XmlEnum("user")] + User, + + [XmlEnum("slot")] + Level, + + [XmlEnum("playlist")] + Playlist, +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Activity/EventType.cs b/ProjectLighthouse/Types/Activity/EventType.cs index 7e0c74d7..e4ec2e9a 100644 --- a/ProjectLighthouse/Types/Activity/EventType.cs +++ b/ProjectLighthouse/Types/Activity/EventType.cs @@ -1,26 +1,69 @@ -namespace LBPUnion.ProjectLighthouse.Types.Activity; +using System.Xml.Serialization; + +namespace LBPUnion.ProjectLighthouse.Types.Activity; public enum EventType { + [XmlEnum("heart_level")] HeartLevel, + + [XmlEnum("unheart_level")] UnheartLevel, + + [XmlEnum("heart_user")] HeartUser, + + [XmlEnum("unheart_user")] UnheartUser, + + [XmlEnum("play_level")] PlayLevel, + + [XmlEnum("rate_level")] RateLevel, + + [XmlEnum("tag_level")] TagLevel, + + [XmlEnum("comment_on_level")] CommentOnLevel, + + [XmlEnum("delete_level_comment")] DeleteLevelComment, + + [XmlEnum("upload_photo")] UploadPhoto, + + [XmlEnum("publish_level")] PublishLevel, + + [XmlEnum("unpublish_level")] UnpublishLevel, + + [XmlEnum("score")] Score, + + [XmlEnum("news_post")] NewsPost, + + [XmlEnum("mm_pick_level")] MMPickLevel, + + [XmlEnum("dpad_rate_level")] DpadRateLevel, + + [XmlEnum("review_level")] ReviewLevel, + + [XmlEnum("comment_on_user")] CommentOnUser, + + [XmlEnum("create_playlist")] CreatePlaylist, + + [XmlEnum("heart_playlist")] HeartPlaylist, + + [XmlEnum("add_level_to_playlist")] AddLevelToPlaylist, } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Activity/IEntityEventHandler.cs b/ProjectLighthouse/Types/Activity/IEntityEventHandler.cs new file mode 100644 index 00000000..982857ce --- /dev/null +++ b/ProjectLighthouse/Types/Activity/IEntityEventHandler.cs @@ -0,0 +1,10 @@ +using LBPUnion.ProjectLighthouse.Database; + +namespace LBPUnion.ProjectLighthouse.Types.Activity; + +public interface IEntityEventHandler +{ + public void OnEntityInserted(DatabaseContext database, T entity) where T : class; + public void OnEntityChanged(DatabaseContext database, T origEntity, T currentEntity) where T : class; + public void OnEntityDeleted(DatabaseContext database, T entity) where T : class; +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Entities/Activity/ActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/ActivityEntity.cs index cdb81944..7a1c5bf1 100644 --- a/ProjectLighthouse/Types/Entities/Activity/ActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/ActivityEntity.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using LBPUnion.ProjectLighthouse.Types.Activity; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; @@ -10,7 +11,7 @@ public class ActivityEntity [Key] public int ActivityId { get; set; } - public long Timestamp { get; set; } + public DateTime Timestamp { get; set; } public int UserId { get; set; } diff --git a/ProjectLighthouse/Types/Entities/Activity/CommentActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/CommentActivityEntity.cs new file mode 100644 index 00000000..0c20175e --- /dev/null +++ b/ProjectLighthouse/Types/Entities/Activity/CommentActivityEntity.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations.Schema; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; + +namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; + +/// +/// Supported event types: CommentOnUser, CommentOnLevel, DeleteLevelComment +/// +public class CommentActivityEntity : ActivityEntity +{ + public int CommentId { get; set; } + + [ForeignKey(nameof(CommentId))] + public CommentEntity Comment { get; set; } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Entities/Activity/CommentActivityEntry.cs b/ProjectLighthouse/Types/Entities/Activity/CommentActivityEntry.cs deleted file mode 100644 index 4aa02e0d..00000000 --- a/ProjectLighthouse/Types/Entities/Activity/CommentActivityEntry.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; - -/// -/// Supported event types: CommentOnUser, CommentOnLevel, DeleteLevelComment -/// -public class CommentActivityEntry -{ - -} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Entities/Activity/PlaylistActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/PlaylistActivityEntity.cs index 502baf4b..fecf6b80 100644 --- a/ProjectLighthouse/Types/Entities/Activity/PlaylistActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/PlaylistActivityEntity.cs @@ -3,6 +3,9 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Level; namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; +/// +/// Supported event types: CreatePlaylist, HeartPlaylist, AddLevelToPlaylist +/// public class PlaylistActivityEntity : ActivityEntity { public int PlaylistId { get; set; } diff --git a/ProjectLighthouse/Types/Entities/Activity/ReviewActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/ReviewActivityEntity.cs new file mode 100644 index 00000000..5c295033 --- /dev/null +++ b/ProjectLighthouse/Types/Entities/Activity/ReviewActivityEntity.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations.Schema; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; + +namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; + +public class ReviewActivityEntity : ActivityEntity +{ + public int ReviewId { get; set; } + + [ForeignKey(nameof(ReviewId))] + public ReviewEntity Review { get; set; } + + // TODO review_modified? +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Entities/Activity/UserActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/UserActivityEntity.cs index 74856367..89d27e8b 100644 --- a/ProjectLighthouse/Types/Entities/Activity/UserActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/UserActivityEntity.cs @@ -1,9 +1,15 @@ -namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; +using System.ComponentModel.DataAnnotations.Schema; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; + +namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; /// /// Supported event types: HeartUser, UnheartUser /// public class UserActivityEntity : ActivityEntity { - + public int TargetUserId { get; set; } + + [ForeignKey(nameof(TargetUserId))] + public UserEntity TargetUser { get; set; } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Entities/Level/ReviewEntity.cs b/ProjectLighthouse/Types/Entities/Level/ReviewEntity.cs index 59091953..c6f18240 100644 --- a/ProjectLighthouse/Types/Entities/Level/ReviewEntity.cs +++ b/ProjectLighthouse/Types/Entities/Level/ReviewEntity.cs @@ -3,7 +3,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; -using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; namespace LBPUnion.ProjectLighthouse.Types.Entities.Level; diff --git a/ProjectLighthouse/Types/Levels/Category.cs b/ProjectLighthouse/Types/Levels/Category.cs index c18a4a00..1b3c08de 100644 --- a/ProjectLighthouse/Types/Levels/Category.cs +++ b/ProjectLighthouse/Types/Levels/Category.cs @@ -5,6 +5,7 @@ using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Filter; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Serialization; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; namespace LBPUnion.ProjectLighthouse.Types.Levels; diff --git a/ProjectLighthouse/Types/Serialization/Activity/Events/GameCommentEvent.cs b/ProjectLighthouse/Types/Serialization/Activity/Events/GameCommentEvent.cs new file mode 100644 index 00000000..93d38257 --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/Events/GameCommentEvent.cs @@ -0,0 +1,55 @@ +using System.Threading.Tasks; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events; + +[XmlInclude(typeof(GameUserCommentEvent))] +[XmlInclude(typeof(GameSlotCommentEvent))] +public class GameCommentEvent : GameEvent +{ + [XmlElement("comment_id")] + public int CommentId { get; set; } +} + +public class GameUserCommentEvent : GameCommentEvent +{ + [XmlElement("object_user")] + public string TargetUsername { get; set; } + + public new async Task PrepareSerialization(DatabaseContext database) + { + await base.PrepareSerialization(database); + + CommentEntity comment = await database.Comments.FindAsync(this.CommentId); + if (comment == null) return; + + UserEntity user = await database.Users.FindAsync(comment.TargetId); + if (user == null) return; + + this.TargetUsername = user.Username; + } +} + +public class GameSlotCommentEvent : GameCommentEvent +{ + [XmlElement("object_slot_id")] + public ReviewSlot TargetSlot { get; set; } + + public new async Task PrepareSerialization(DatabaseContext database) + { + await base.PrepareSerialization(database); + + CommentEntity comment = await database.Comments.FindAsync(this.CommentId); + if (comment == null) return; + + SlotEntity slot = await database.Slots.FindAsync(comment.TargetId); + + if (slot == null) return; + + this.TargetSlot = ReviewSlot.CreateFromEntity(slot); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/Activity/Events/GameEvent.cs b/ProjectLighthouse/Types/Serialization/Activity/Events/GameEvent.cs new file mode 100644 index 00000000..db9d6dcf --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/Events/GameEvent.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Extensions; +using LBPUnion.ProjectLighthouse.Types.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events; + +[XmlInclude(typeof(GameCommentEvent))] +[XmlInclude(typeof(GamePhotoUploadEvent))] +[XmlInclude(typeof(GamePlayLevelEvent))] +[XmlInclude(typeof(GameReviewEvent))] +[XmlInclude(typeof(GameScoreEvent))] +[XmlInclude(typeof(GameHeartLevelEvent))] +[XmlInclude(typeof(GameHeartUserEvent))] +public class GameEvent : ILbpSerializable, INeedsPreparationForSerialization +{ + [XmlIgnore] + private int UserId { get; set; } + + [XmlAttribute("type")] + public EventType Type { get; set; } + + [XmlElement("timestamp")] + public long Timestamp { get; set; } + + [XmlElement("actor")] + public string Username { get; set; } + + protected async Task PrepareSerialization(DatabaseContext database) + { + Console.WriteLine($@"SERIALIZATION!! {this.UserId} - {this.GetHashCode()}"); + UserEntity user = await database.Users.FindAsync(this.UserId); + if (user == null) return; + this.Username = user.Username; + } + + public static IEnumerable CreateFromActivityGroups(IGrouping group) + { + List events = new(); + + // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault + // Events with Count need special treatment + switch (group.Key) + { + case EventType.PlayLevel: + { + if (group.First() is not LevelActivityEntity levelActivity) break; + + events.Add(new GamePlayLevelEvent + { + Slot = new ReviewSlot + { + SlotId = levelActivity.SlotId, + }, + Count = group.Count(), + UserId = levelActivity.UserId, + Timestamp = levelActivity.Timestamp.ToUnixTimeMilliseconds(), + Type = levelActivity.Type, + }); + break; + } + case EventType.PublishLevel: + { + if (group.First() is not LevelActivityEntity levelActivity) break; + + events.Add(new GamePublishLevelEvent + { + Slot = new ReviewSlot + { + SlotId = levelActivity.SlotId, + }, + Count = group.Count(), + UserId = levelActivity.UserId, + Timestamp = levelActivity.Timestamp.ToUnixTimeMilliseconds(), + Type = levelActivity.Type, + }); + break; + } + // Everything else can be handled as normal + default: events.AddRange(group.Select(CreateFromActivity)); + break; + } + return events.AsEnumerable(); + } + + private static GameEvent CreateFromActivity(ActivityEntity activity) + { + GameEvent gameEvent = activity.Type switch + { + EventType.PlayLevel => new GamePlayLevelEvent + { + Slot = new ReviewSlot + { + SlotId = ((LevelActivityEntity)activity).SlotId, + }, + }, + EventType.CommentOnLevel => new GameSlotCommentEvent + { + CommentId = ((CommentActivityEntity)activity).CommentId, + }, + EventType.CommentOnUser => new GameUserCommentEvent + { + CommentId = ((CommentActivityEntity)activity).CommentId, + }, + EventType.HeartUser or EventType.UnheartUser => new GameHeartUserEvent + { + TargetUserId = ((UserActivityEntity)activity).TargetUserId, + }, + EventType.HeartLevel or EventType.UnheartLevel => new GameHeartLevelEvent + { + TargetSlot = new ReviewSlot + { + SlotId = ((LevelActivityEntity)activity).SlotId, + }, + }, + _ => new GameEvent(), + }; + gameEvent.UserId = activity.UserId; + gameEvent.Type = activity.Type; + gameEvent.Timestamp = activity.Timestamp.ToUnixTimeMilliseconds(); + return gameEvent; + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/Activity/Events/GameHeartEvent.cs b/ProjectLighthouse/Types/Serialization/Activity/Events/GameHeartEvent.cs new file mode 100644 index 00000000..251d3f92 --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/Events/GameHeartEvent.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events; + +public class GameHeartUserEvent : GameEvent +{ + [XmlIgnore] + public int TargetUserId { get; set; } + + [XmlElement("object_user")] + public string TargetUsername { get; set; } + + public new async Task PrepareSerialization(DatabaseContext database) + { + await base.PrepareSerialization(database); + + UserEntity targetUser = await database.Users.FindAsync(this.TargetUserId); + if (targetUser == null) return; + + this.TargetUsername = targetUser.Username; + } +} + +public class GameHeartLevelEvent : GameEvent +{ + [XmlElement("object_slot_id")] + public ReviewSlot TargetSlot { get; set; } + + public new async Task PrepareSerialization(DatabaseContext database) + { + await base.PrepareSerialization(database); + + SlotEntity slot = await database.Slots.FindAsync(this.TargetSlot.SlotId); + if (slot == null) return; + + this.TargetSlot = ReviewSlot.CreateFromEntity(slot); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/Activity/Events/GamePhotoUploadEvent.cs b/ProjectLighthouse/Types/Serialization/Activity/Events/GamePhotoUploadEvent.cs new file mode 100644 index 00000000..527c9c7b --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/Events/GamePhotoUploadEvent.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events; + +public class GamePhotoUploadEvent : GameEvent +{ + [XmlElement("photo_id")] + public int PhotoId { get; set; } + + [XmlElement("object_slot_id")] + [DefaultValue(null)] + public ReviewSlot SlotId { get; set; } + + [XmlElement("user_in_photo")] + public List PhotoParticipants { get; set; } + + public new async Task PrepareSerialization(DatabaseContext database) + { + await base.PrepareSerialization(database); + + PhotoEntity photo = await database.Photos.Where(p => p.PhotoId == this.PhotoId) + .Include(p => p.PhotoSubjects) + .ThenInclude(ps => ps.User) + .FirstOrDefaultAsync(); + if (photo == null) return; + + this.PhotoParticipants = photo.PhotoSubjects.Select(ps => ps.User.Username).ToList(); + + if (photo.SlotId == null) return; + + SlotEntity slot = await database.Slots.FindAsync(photo.SlotId); + if (slot == null) return; + + this.SlotId = ReviewSlot.CreateFromEntity(slot); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/Activity/Events/GamePlayLevelEvent.cs b/ProjectLighthouse/Types/Serialization/Activity/Events/GamePlayLevelEvent.cs new file mode 100644 index 00000000..af00048c --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/Events/GamePlayLevelEvent.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events; + +public class GamePlayLevelEvent : GameEvent +{ + [XmlElement("object_slot_id")] + public ReviewSlot Slot { get; set; } + + [XmlElement("count")] + public int Count { get; set; } = 1; + + public new async Task PrepareSerialization(DatabaseContext database) + { + await base.PrepareSerialization(database); + + SlotEntity slot = await database.Slots.FindAsync(this.Slot.SlotId); + if (slot == null) return; + + this.Slot = ReviewSlot.CreateFromEntity(slot); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/Activity/Events/GamePublishLevelEvent.cs b/ProjectLighthouse/Types/Serialization/Activity/Events/GamePublishLevelEvent.cs new file mode 100644 index 00000000..74b35b7f --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/Events/GamePublishLevelEvent.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events; + +public class GamePublishLevelEvent : GameEvent +{ + [XmlElement("object_slot_id")] + public ReviewSlot Slot { get; set; } + + [XmlElement("republish")] + public bool IsRepublish { get; set; } + + [XmlElement("count")] + public int Count { get; set; } + + public new async Task PrepareSerialization(DatabaseContext database) + { + await base.PrepareSerialization(database); + + SlotEntity slot = await database.Slots.FindAsync(this.Slot.SlotId); + if (slot == null) return; + + this.Slot = ReviewSlot.CreateFromEntity(slot); + // TODO does this work? + this.IsRepublish = slot.LastUpdated == slot.FirstUploaded; + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/Activity/Events/GameReviewEvent.cs b/ProjectLighthouse/Types/Serialization/Activity/Events/GameReviewEvent.cs new file mode 100644 index 00000000..e089b987 --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/Events/GameReviewEvent.cs @@ -0,0 +1,32 @@ +using System.ComponentModel; +using System.Threading.Tasks; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events; + +public class GameReviewEvent : GameEvent +{ + [XmlElement("slot_id")] + public ReviewSlot Slot { get; set; } + + [XmlElement("review_id")] + public int ReviewId { get; set; } + + [XmlElement("review_modified")] + [DefaultValue(0)] + public long ReviewTimestamp { get; set; } + + public new async Task PrepareSerialization(DatabaseContext database) + { + ReviewEntity review = await database.Reviews.FindAsync(this.ReviewId); + if (review == null) return; + + SlotEntity slot = await database.Slots.FindAsync(review.SlotId); + if (slot == null) return; + + this.Slot = ReviewSlot.CreateFromEntity(slot); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/Activity/Events/GameScoreEvent.cs b/ProjectLighthouse/Types/Serialization/Activity/Events/GameScoreEvent.cs new file mode 100644 index 00000000..bed5c1ac --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/Events/GameScoreEvent.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events; + +public class GameScoreEvent : GameEvent +{ + [XmlIgnore] + public int ScoreId { get; set; } + + [XmlElement("object_slot_id")] + public ReviewSlot Slot { get; set; } + + [XmlElement("score")] + public int Score { get; set; } + + [XmlElement("user_count")] + public int UserCount { get; set; } + + public new async Task PrepareSerialization(DatabaseContext database) + { + await base.PrepareSerialization(database); + + ScoreEntity score = await database.Scores.FindAsync(this.ScoreId); + if (score == null) return; + + SlotEntity slot = await database.Slots.FindAsync(score.SlotId); + if (slot == null) return; + + this.Score = score.Points; + //TODO is this correct? + this.UserCount = score.Type; + + this.Slot = ReviewSlot.CreateFromEntity(slot); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/Activity/GameSlotStreamGroup.cs b/ProjectLighthouse/Types/Serialization/Activity/GameSlotStreamGroup.cs new file mode 100644 index 00000000..9c145fb5 --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/GameSlotStreamGroup.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity; + +public class GameSlotStreamGroup : GameStreamGroup, INeedsPreparationForSerialization +{ + [XmlElement("slot_id")] + public ReviewSlot Slot { get; set; } + + public async Task PrepareSerialization(DatabaseContext database) + { + SlotEntity slot = await database.Slots.FindAsync(this.Slot.SlotId); + if (slot == null) return; + + this.Slot = ReviewSlot.CreateFromEntity(slot); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/Activity/GameStream.cs b/ProjectLighthouse/Types/Serialization/Activity/GameStream.cs new file mode 100644 index 00000000..95dd7929 --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/GameStream.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Types.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; +using LBPUnion.ProjectLighthouse.Types.Entities.Token; +using LBPUnion.ProjectLighthouse.Types.Serialization.Slot; +using LBPUnion.ProjectLighthouse.Types.Serialization.User; +using LBPUnion.ProjectLighthouse.Types.Users; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity; + +/// +/// The global stream object, contains all +/// +[XmlRoot("stream")] +public class GameStream : ILbpSerializable, INeedsPreparationForSerialization +{ + [XmlIgnore] + private List SlotIds { get; set; } + + [XmlIgnore] + private List UserIds { get; set; } + + [XmlIgnore] + private int TargetUserId { get; set; } + + [XmlIgnore] + private GameVersion TargetGame { get; set; } + + [XmlElement("start_timestamp")] + public long StartTimestamp { get; set; } + + [XmlElement("end_timestamp")] + public long EndTimestamp { get; set; } + + [XmlArray("groups")] + [XmlArrayItem("group")] + public List Groups { get; set; } + + [XmlArray("slots")] + [XmlArrayItem("slot")] + public List Slots { get; set; } + + [XmlArray("users")] + [XmlArrayItem("user")] + public List Users { get; set; } + + [XmlArray("news")] + [XmlArrayItem("item")] + public List News { get; set; } + //TODO implement lbp1 and lbp2 news objects + + public async Task PrepareSerialization(DatabaseContext database) + { + if (this.SlotIds.Count > 0) + { + this.Slots = new List(); + foreach (int slotId in this.SlotIds) + { + SlotEntity slot = await database.Slots.FindAsync(slotId); + if (slot == null) continue; + + this.Slots.Add(SlotBase.CreateFromEntity(slot, this.TargetGame, this.TargetUserId)); + } + } + + if (this.UserIds.Count > 0) + { + this.Users = new List(); + foreach (int userId in this.UserIds) + { + UserEntity user = await database.Users.FindAsync(userId); + if (user == null) continue; + + this.Users.Add(GameUser.CreateFromEntity(user, this.TargetGame)); + } + } + } + + public static async Task CreateFromEntityResult + ( + DatabaseContext database, + GameTokenEntity token, + List> results, + long startTimestamp, + long endTimestamp + ) + { + List slotIds = results.Where(g => g.Key.TargetSlotId != null && g.Key.TargetSlotId.Value != 0) + .Select(g => g.Key.TargetSlotId.Value) + .ToList(); + Console.WriteLine($@"slotIds: {string.Join(",", slotIds)}"); + List userIds = results.Where(g => g.Key.TargetUserId != null && g.Key.TargetUserId.Value != 0) + .Select(g => g.Key.TargetUserId.Value) + .Distinct() + .Union(results.Select(g => g.Key.UserId)) + .ToList(); + // Cache target levels and users within DbContext + await database.Slots.Where(s => slotIds.Contains(s.SlotId)).LoadAsync(); + await database.Users.Where(u => userIds.Contains(u.UserId)).LoadAsync(); + Console.WriteLine($@"userIds: {string.Join(",", userIds)}"); + Console.WriteLine($@"Stream contains {slotIds.Count} slots and {userIds.Count} users"); + GameStream gameStream = new() + { + TargetUserId = token.UserId, + TargetGame = token.GameVersion, + StartTimestamp = startTimestamp, + EndTimestamp = endTimestamp, + SlotIds = slotIds, + UserIds = userIds, + Groups = new List(), + }; + foreach (IGrouping group in results) + { + gameStream.Groups.Add(GameStreamGroup.CreateFromGrouping(group)); + } + + return gameStream; + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/Activity/GameStreamGroup.cs b/ProjectLighthouse/Types/Serialization/Activity/GameStreamGroup.cs new file mode 100644 index 00000000..ef783f54 --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/GameStreamGroup.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Types.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Activity; +using LBPUnion.ProjectLighthouse.Types.Serialization.Activity.Events; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity; + +/// +/// Top level groups generally contain all events for a given level or user +/// +/// The sub-groups are always and contain all activities from a single user +/// for the top level group entity +/// +/// +[XmlInclude(typeof(GameUserStreamGroup))] +[XmlInclude(typeof(GameSlotStreamGroup))] +public class GameStreamGroup : ILbpSerializable +{ + [XmlAttribute("type")] + public ActivityGroupType Type { get; set; } + + [XmlElement("timestamp")] + public long Timestamp { get; set; } + + [XmlArray("subgroups")] + [XmlArrayItem("group")] + [DefaultValue(null)] + public List Groups { get; set; } + + [XmlArray("events")] + [XmlArrayItem("event")] + [DefaultValue(null)] + public List Events { get; set; } + + public static GameStreamGroup CreateFromGrouping(IGrouping group) + { + ActivityGroupType type = group.Key.GroupType; + GameStreamGroup gameGroup = type switch + { + ActivityGroupType.Level => new GameSlotStreamGroup + { + Slot = new ReviewSlot + { + SlotId = group.Key.TargetId, + }, + }, + ActivityGroupType.User => new GameUserStreamGroup + { + UserId = group.Key.TargetId, + }, + _ => new GameStreamGroup(), + }; + gameGroup.Timestamp = new DateTimeOffset(group.Select(a => a.Timestamp).MaxBy(a => a)).ToUnixTimeMilliseconds(); + gameGroup.Type = type; + + List> eventGroups = group.OrderByDescending(a => a.Timestamp).GroupBy(g => g.Type).ToList(); + //TODO removeme debug + foreach (IGrouping bruh in eventGroups) + { + Console.WriteLine($@"group key: {bruh.Key}, count={bruh.Count()}"); + } + gameGroup.Groups = new List + { + new GameUserStreamGroup + { + UserId = group.Key.UserId, + Type = ActivityGroupType.User, + Timestamp = gameGroup.Timestamp, + Events = eventGroups.SelectMany(GameEvent.CreateFromActivityGroups).ToList(), + }, + }; + + return gameGroup; + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/Activity/GameUserStreamGroup.cs b/ProjectLighthouse/Types/Serialization/Activity/GameUserStreamGroup.cs new file mode 100644 index 00000000..bc7cf53b --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Activity/GameUserStreamGroup.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Types.Entities.Profile; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity; + +public class GameUserStreamGroup : GameStreamGroup, INeedsPreparationForSerialization +{ + [XmlIgnore] + public int UserId { get; set; } + + [XmlElement("user_id")] + public string Username { get; set; } + + public async Task PrepareSerialization(DatabaseContext database) + { + UserEntity user = await database.Users.FindAsync(this.UserId); + if (user == null) return; + + this.Username = user.Username; + } + + public static GameUserStreamGroup Create(int userId) => + new() + { + UserId = userId, + }; +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/CommentListResponse.cs b/ProjectLighthouse/Types/Serialization/Comment/CommentListResponse.cs similarity index 83% rename from ProjectLighthouse/Types/Serialization/CommentListResponse.cs rename to ProjectLighthouse/Types/Serialization/Comment/CommentListResponse.cs index 35d9023e..b9b11d0c 100644 --- a/ProjectLighthouse/Types/Serialization/CommentListResponse.cs +++ b/ProjectLighthouse/Types/Serialization/Comment/CommentListResponse.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Comment; [XmlRoot("comments")] public struct CommentListResponse : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/GameComment.cs b/ProjectLighthouse/Types/Serialization/Comment/GameComment.cs similarity index 97% rename from ProjectLighthouse/Types/Serialization/GameComment.cs rename to ProjectLighthouse/Types/Serialization/Comment/GameComment.cs index 07919f5e..16597d13 100644 --- a/ProjectLighthouse/Types/Serialization/GameComment.cs +++ b/ProjectLighthouse/Types/Serialization/Comment/GameComment.cs @@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Comment; [XmlRoot("comment")] [XmlType("comment")] diff --git a/ProjectLighthouse/Types/Serialization/GamePhoto.cs b/ProjectLighthouse/Types/Serialization/Photo/GamePhoto.cs similarity index 98% rename from ProjectLighthouse/Types/Serialization/GamePhoto.cs rename to ProjectLighthouse/Types/Serialization/Photo/GamePhoto.cs index 67bb2dfb..3c0cdd9e 100644 --- a/ProjectLighthouse/Types/Serialization/GamePhoto.cs +++ b/ProjectLighthouse/Types/Serialization/Photo/GamePhoto.cs @@ -9,7 +9,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Levels; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Photo; [XmlRoot("photo")] [XmlType("photo")] diff --git a/ProjectLighthouse/Types/Serialization/GamePhotoSubject.cs b/ProjectLighthouse/Types/Serialization/Photo/GamePhotoSubject.cs similarity index 91% rename from ProjectLighthouse/Types/Serialization/GamePhotoSubject.cs rename to ProjectLighthouse/Types/Serialization/Photo/GamePhotoSubject.cs index 94cbd50c..914b8227 100644 --- a/ProjectLighthouse/Types/Serialization/GamePhotoSubject.cs +++ b/ProjectLighthouse/Types/Serialization/Photo/GamePhotoSubject.cs @@ -1,7 +1,7 @@ using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Photo; [XmlType("subject")] [XmlRoot("subject")] diff --git a/ProjectLighthouse/Types/Serialization/PhotoListResponse.cs b/ProjectLighthouse/Types/Serialization/Photo/PhotoListResponse.cs similarity index 83% rename from ProjectLighthouse/Types/Serialization/PhotoListResponse.cs rename to ProjectLighthouse/Types/Serialization/Photo/PhotoListResponse.cs index a8d64028..d4f5fbfd 100644 --- a/ProjectLighthouse/Types/Serialization/PhotoListResponse.cs +++ b/ProjectLighthouse/Types/Serialization/Photo/PhotoListResponse.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Photo; [XmlRoot("photos")] public struct PhotoListResponse : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/PhotoSlot.cs b/ProjectLighthouse/Types/Serialization/Photo/PhotoSlot.cs similarity index 88% rename from ProjectLighthouse/Types/Serialization/PhotoSlot.cs rename to ProjectLighthouse/Types/Serialization/Photo/PhotoSlot.cs index c4699720..82dea982 100644 --- a/ProjectLighthouse/Types/Serialization/PhotoSlot.cs +++ b/ProjectLighthouse/Types/Serialization/Photo/PhotoSlot.cs @@ -3,7 +3,7 @@ using System.ComponentModel; using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Types.Levels; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Photo; [XmlRoot("slot")] public class PhotoSlot : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/Author.cs b/ProjectLighthouse/Types/Serialization/Playlist/Author.cs similarity index 80% rename from ProjectLighthouse/Types/Serialization/Author.cs rename to ProjectLighthouse/Types/Serialization/Playlist/Author.cs index 7de0599a..cfd577b3 100644 --- a/ProjectLighthouse/Types/Serialization/Author.cs +++ b/ProjectLighthouse/Types/Serialization/Playlist/Author.cs @@ -1,6 +1,6 @@ using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Playlist; [XmlRoot("author")] public struct Author : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/GamePlaylist.cs b/ProjectLighthouse/Types/Serialization/Playlist/GamePlaylist.cs similarity index 97% rename from ProjectLighthouse/Types/Serialization/GamePlaylist.cs rename to ProjectLighthouse/Types/Serialization/Playlist/GamePlaylist.cs index 5ced447c..90d89329 100644 --- a/ProjectLighthouse/Types/Serialization/GamePlaylist.cs +++ b/ProjectLighthouse/Types/Serialization/Playlist/GamePlaylist.cs @@ -10,7 +10,7 @@ using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Playlist; [XmlRoot("playlist")] public class GamePlaylist : ILbpSerializable, INeedsPreparationForSerialization diff --git a/ProjectLighthouse/Types/Serialization/GenericPlaylistResponse.cs b/ProjectLighthouse/Types/Serialization/Playlist/GenericPlaylistResponse.cs similarity index 93% rename from ProjectLighthouse/Types/Serialization/GenericPlaylistResponse.cs rename to ProjectLighthouse/Types/Serialization/Playlist/GenericPlaylistResponse.cs index ba85d8c8..f0f1f0f7 100644 --- a/ProjectLighthouse/Types/Serialization/GenericPlaylistResponse.cs +++ b/ProjectLighthouse/Types/Serialization/Playlist/GenericPlaylistResponse.cs @@ -2,7 +2,7 @@ using System.ComponentModel; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Playlist; public struct GenericPlaylistResponse : ILbpSerializable, IHasCustomRoot where T : ILbpSerializable { diff --git a/ProjectLighthouse/Types/Serialization/IconList.cs b/ProjectLighthouse/Types/Serialization/Playlist/IconList.cs similarity index 82% rename from ProjectLighthouse/Types/Serialization/IconList.cs rename to ProjectLighthouse/Types/Serialization/Playlist/IconList.cs index 87a6f6cb..9d0e7f1c 100644 --- a/ProjectLighthouse/Types/Serialization/IconList.cs +++ b/ProjectLighthouse/Types/Serialization/Playlist/IconList.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Playlist; public struct IconList : ILbpSerializable { diff --git a/ProjectLighthouse/Types/Serialization/PlaylistResponse.cs b/ProjectLighthouse/Types/Serialization/Playlist/PlaylistResponse.cs similarity index 85% rename from ProjectLighthouse/Types/Serialization/PlaylistResponse.cs rename to ProjectLighthouse/Types/Serialization/Playlist/PlaylistResponse.cs index 81a46003..a5b309ab 100644 --- a/ProjectLighthouse/Types/Serialization/PlaylistResponse.cs +++ b/ProjectLighthouse/Types/Serialization/Playlist/PlaylistResponse.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Playlist; [XmlRoot("playlists")] public struct PlaylistResponse : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/GameReview.cs b/ProjectLighthouse/Types/Serialization/Review/GameReview.cs similarity index 98% rename from ProjectLighthouse/Types/Serialization/GameReview.cs rename to ProjectLighthouse/Types/Serialization/Review/GameReview.cs index 7dba385e..1da3b77b 100644 --- a/ProjectLighthouse/Types/Serialization/GameReview.cs +++ b/ProjectLighthouse/Types/Serialization/Review/GameReview.cs @@ -8,7 +8,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Levels; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Review; [XmlRoot("deleted_by")] public enum DeletedBy diff --git a/ProjectLighthouse/Types/Serialization/ReviewResponse.cs b/ProjectLighthouse/Types/Serialization/Review/ReviewResponse.cs similarity index 90% rename from ProjectLighthouse/Types/Serialization/ReviewResponse.cs rename to ProjectLighthouse/Types/Serialization/Review/ReviewResponse.cs index a15915dd..e13e99bb 100644 --- a/ProjectLighthouse/Types/Serialization/ReviewResponse.cs +++ b/ProjectLighthouse/Types/Serialization/Review/ReviewResponse.cs @@ -2,7 +2,7 @@ using System.ComponentModel; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Review; [XmlRoot("reviews")] public struct ReviewResponse : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/Review/ReviewSlot.cs b/ProjectLighthouse/Types/Serialization/Review/ReviewSlot.cs new file mode 100644 index 00000000..11715e17 --- /dev/null +++ b/ProjectLighthouse/Types/Serialization/Review/ReviewSlot.cs @@ -0,0 +1,22 @@ +using System.Xml.Serialization; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; +using LBPUnion.ProjectLighthouse.Types.Levels; + +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Review; + +[XmlRoot("slot")] +public class ReviewSlot : ILbpSerializable +{ + [XmlAttribute("type")] + public SlotType SlotType { get; set; } + + [XmlText] + public int SlotId { get; set; } + + public static ReviewSlot CreateFromEntity(SlotEntity slot) => + new() + { + SlotType = slot.Type, + SlotId = slot.Type == SlotType.User ? slot.SlotId : slot.InternalSlotId, + }; +} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/ReviewSlot.cs b/ProjectLighthouse/Types/Serialization/ReviewSlot.cs deleted file mode 100644 index f4148adf..00000000 --- a/ProjectLighthouse/Types/Serialization/ReviewSlot.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Xml.Serialization; -using LBPUnion.ProjectLighthouse.Types.Levels; - -namespace LBPUnion.ProjectLighthouse.Types.Serialization; - -[XmlRoot("slot")] -public class ReviewSlot : ILbpSerializable -{ - [XmlAttribute("type")] - public SlotType SlotType { get; set; } - - [XmlText] - public int SlotId { get; set; } -} \ No newline at end of file diff --git a/ProjectLighthouse/Types/Serialization/GameScore.cs b/ProjectLighthouse/Types/Serialization/Score/GameScore.cs similarity index 95% rename from ProjectLighthouse/Types/Serialization/GameScore.cs rename to ProjectLighthouse/Types/Serialization/Score/GameScore.cs index 1195ce96..9c10ae73 100644 --- a/ProjectLighthouse/Types/Serialization/GameScore.cs +++ b/ProjectLighthouse/Types/Serialization/Score/GameScore.cs @@ -6,7 +6,7 @@ using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Score; [XmlRoot("playRecord")] [XmlType("playRecord")] diff --git a/ProjectLighthouse/Types/Serialization/MultiScoreboardResponse.cs b/ProjectLighthouse/Types/Serialization/Score/MultiScoreboardResponse.cs similarity index 93% rename from ProjectLighthouse/Types/Serialization/MultiScoreboardResponse.cs rename to ProjectLighthouse/Types/Serialization/Score/MultiScoreboardResponse.cs index 831e5efe..3d00f141 100644 --- a/ProjectLighthouse/Types/Serialization/MultiScoreboardResponse.cs +++ b/ProjectLighthouse/Types/Serialization/Score/MultiScoreboardResponse.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Score; [XmlRoot("scoreboards")] public class MultiScoreboardResponse : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/ScoreboardResponse.cs b/ProjectLighthouse/Types/Serialization/Score/ScoreboardResponse.cs similarity index 94% rename from ProjectLighthouse/Types/Serialization/ScoreboardResponse.cs rename to ProjectLighthouse/Types/Serialization/Score/ScoreboardResponse.cs index f9c45763..2ee27c0b 100644 --- a/ProjectLighthouse/Types/Serialization/ScoreboardResponse.cs +++ b/ProjectLighthouse/Types/Serialization/Score/ScoreboardResponse.cs @@ -2,7 +2,7 @@ using System.ComponentModel; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Score; public struct ScoreboardResponse: ILbpSerializable, IHasCustomRoot { diff --git a/ProjectLighthouse/Types/Serialization/CategoryListResponse.cs b/ProjectLighthouse/Types/Serialization/Slot/CategoryListResponse.cs similarity index 93% rename from ProjectLighthouse/Types/Serialization/CategoryListResponse.cs rename to ProjectLighthouse/Types/Serialization/Slot/CategoryListResponse.cs index 904fb290..faa24508 100644 --- a/ProjectLighthouse/Types/Serialization/CategoryListResponse.cs +++ b/ProjectLighthouse/Types/Serialization/Slot/CategoryListResponse.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Slot; [XmlRoot("categories")] public class CategoryListResponse : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/GameCategory.cs b/ProjectLighthouse/Types/Serialization/Slot/GameCategory.cs similarity index 95% rename from ProjectLighthouse/Types/Serialization/GameCategory.cs rename to ProjectLighthouse/Types/Serialization/Slot/GameCategory.cs index e097de97..2f0a2069 100644 --- a/ProjectLighthouse/Types/Serialization/GameCategory.cs +++ b/ProjectLighthouse/Types/Serialization/Slot/GameCategory.cs @@ -2,7 +2,7 @@ using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Types.Levels; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Slot; [XmlRoot("category")] public class GameCategory : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/GameDeveloperSlot.cs b/ProjectLighthouse/Types/Serialization/Slot/GameDeveloperSlot.cs similarity index 96% rename from ProjectLighthouse/Types/Serialization/GameDeveloperSlot.cs rename to ProjectLighthouse/Types/Serialization/Slot/GameDeveloperSlot.cs index 3de55ee4..d7a67c6c 100644 --- a/ProjectLighthouse/Types/Serialization/GameDeveloperSlot.cs +++ b/ProjectLighthouse/Types/Serialization/Slot/GameDeveloperSlot.cs @@ -7,7 +7,7 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Profile; using LBPUnion.ProjectLighthouse.Types.Levels; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Slot; [XmlRoot("slot")] public class GameDeveloperSlot : SlotBase, INeedsPreparationForSerialization diff --git a/ProjectLighthouse/Types/Serialization/GameUserSlot.cs b/ProjectLighthouse/Types/Serialization/Slot/GameUserSlot.cs similarity index 98% rename from ProjectLighthouse/Types/Serialization/GameUserSlot.cs rename to ProjectLighthouse/Types/Serialization/Slot/GameUserSlot.cs index 978818be..11890cce 100644 --- a/ProjectLighthouse/Types/Serialization/GameUserSlot.cs +++ b/ProjectLighthouse/Types/Serialization/Slot/GameUserSlot.cs @@ -13,10 +13,12 @@ using LBPUnion.ProjectLighthouse.Types.Entities.Interaction; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Levels; using LBPUnion.ProjectLighthouse.Types.Misc; +using LBPUnion.ProjectLighthouse.Types.Serialization.Review; +using LBPUnion.ProjectLighthouse.Types.Serialization.User; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Slot; [XmlRoot("slot")] public class GameUserSlot : SlotBase, INeedsPreparationForSerialization diff --git a/ProjectLighthouse/Types/Serialization/GenericSlotResponse.cs b/ProjectLighthouse/Types/Serialization/Slot/GenericSlotResponse.cs similarity index 95% rename from ProjectLighthouse/Types/Serialization/GenericSlotResponse.cs rename to ProjectLighthouse/Types/Serialization/Slot/GenericSlotResponse.cs index b00e8575..036c02c5 100644 --- a/ProjectLighthouse/Types/Serialization/GenericSlotResponse.cs +++ b/ProjectLighthouse/Types/Serialization/Slot/GenericSlotResponse.cs @@ -3,7 +3,7 @@ using System.ComponentModel; using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Types.Filter; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Slot; public struct GenericSlotResponse : ILbpSerializable, IHasCustomRoot { diff --git a/ProjectLighthouse/Types/Serialization/PlanetStatsResponse.cs b/ProjectLighthouse/Types/Serialization/Slot/PlanetStatsResponse.cs similarity index 88% rename from ProjectLighthouse/Types/Serialization/PlanetStatsResponse.cs rename to ProjectLighthouse/Types/Serialization/Slot/PlanetStatsResponse.cs index 0457d282..9dbcd2e5 100644 --- a/ProjectLighthouse/Types/Serialization/PlanetStatsResponse.cs +++ b/ProjectLighthouse/Types/Serialization/Slot/PlanetStatsResponse.cs @@ -1,6 +1,6 @@ using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Slot; [XmlRoot("planetStats")] public class PlanetStatsResponse : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/SlotBase.cs b/ProjectLighthouse/Types/Serialization/Slot/SlotBase.cs similarity index 95% rename from ProjectLighthouse/Types/Serialization/SlotBase.cs rename to ProjectLighthouse/Types/Serialization/Slot/SlotBase.cs index 49d3ce60..9778a941 100644 --- a/ProjectLighthouse/Types/Serialization/SlotBase.cs +++ b/ProjectLighthouse/Types/Serialization/Slot/SlotBase.cs @@ -3,9 +3,10 @@ using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Token; using LBPUnion.ProjectLighthouse.Types.Levels; +using LBPUnion.ProjectLighthouse.Types.Serialization.User; using LBPUnion.ProjectLighthouse.Types.Users; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Slot; [XmlInclude(typeof(GameUserSlot))] [XmlInclude(typeof(GameDeveloperSlot))] @@ -49,7 +50,7 @@ public abstract class SlotBase : ILbpSerializable public static SlotBase CreateFromEntity(SlotEntity slot, GameTokenEntity token) => CreateFromEntity(slot, token.GameVersion, token.UserId); - private static SlotBase CreateFromEntity(SlotEntity slot, GameVersion targetGame, int targetUserId) + public static SlotBase CreateFromEntity(SlotEntity slot, GameVersion targetGame, int targetUserId) { if (slot == null) { diff --git a/ProjectLighthouse/Types/Serialization/SlotResourceResponse.cs b/ProjectLighthouse/Types/Serialization/Slot/SlotResourceResponse.cs similarity index 86% rename from ProjectLighthouse/Types/Serialization/SlotResourceResponse.cs rename to ProjectLighthouse/Types/Serialization/Slot/SlotResourceResponse.cs index 7f9954db..585f4a94 100644 --- a/ProjectLighthouse/Types/Serialization/SlotResourceResponse.cs +++ b/ProjectLighthouse/Types/Serialization/Slot/SlotResourceResponse.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.Slot; [XmlRoot("slot")] public struct SlotResourceResponse : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/FriendResponse.cs b/ProjectLighthouse/Types/Serialization/User/FriendResponse.cs similarity index 85% rename from ProjectLighthouse/Types/Serialization/FriendResponse.cs rename to ProjectLighthouse/Types/Serialization/User/FriendResponse.cs index bf12e68c..ed8abca8 100644 --- a/ProjectLighthouse/Types/Serialization/FriendResponse.cs +++ b/ProjectLighthouse/Types/Serialization/User/FriendResponse.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.User; [XmlRoot("npdata")] public struct FriendResponse : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/GameUser.cs b/ProjectLighthouse/Types/Serialization/User/GameUser.cs similarity index 99% rename from ProjectLighthouse/Types/Serialization/GameUser.cs rename to ProjectLighthouse/Types/Serialization/User/GameUser.cs index cb365ed1..054e3b6d 100644 --- a/ProjectLighthouse/Types/Serialization/GameUser.cs +++ b/ProjectLighthouse/Types/Serialization/User/GameUser.cs @@ -11,7 +11,7 @@ using LBPUnion.ProjectLighthouse.Types.Misc; using LBPUnion.ProjectLighthouse.Types.Users; using Microsoft.EntityFrameworkCore; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.User; [XmlRoot("user")] public class GameUser : ILbpSerializable, INeedsPreparationForSerialization diff --git a/ProjectLighthouse/Types/Serialization/GenericUserResponse.cs b/ProjectLighthouse/Types/Serialization/User/GenericUserResponse.cs similarity index 95% rename from ProjectLighthouse/Types/Serialization/GenericUserResponse.cs rename to ProjectLighthouse/Types/Serialization/User/GenericUserResponse.cs index ef97d42c..43084165 100644 --- a/ProjectLighthouse/Types/Serialization/GenericUserResponse.cs +++ b/ProjectLighthouse/Types/Serialization/User/GenericUserResponse.cs @@ -3,7 +3,7 @@ using System.ComponentModel; using System.Xml.Serialization; using LBPUnion.ProjectLighthouse.Types.Filter; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.User; public struct GenericUserResponse : ILbpSerializable, IHasCustomRoot where T : ILbpSerializable { diff --git a/ProjectLighthouse/Types/Serialization/MinimalUserListResponse.cs b/ProjectLighthouse/Types/Serialization/User/MinimalUserListResponse.cs similarity index 89% rename from ProjectLighthouse/Types/Serialization/MinimalUserListResponse.cs rename to ProjectLighthouse/Types/Serialization/User/MinimalUserListResponse.cs index c289bdc0..be99ba4c 100644 --- a/ProjectLighthouse/Types/Serialization/MinimalUserListResponse.cs +++ b/ProjectLighthouse/Types/Serialization/User/MinimalUserListResponse.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.User; [XmlRoot("users")] public struct MinimalUserListResponse : ILbpSerializable diff --git a/ProjectLighthouse/Types/Serialization/NpHandle.cs b/ProjectLighthouse/Types/Serialization/User/NpHandle.cs similarity index 86% rename from ProjectLighthouse/Types/Serialization/NpHandle.cs rename to ProjectLighthouse/Types/Serialization/User/NpHandle.cs index c7952936..8138f0ae 100644 --- a/ProjectLighthouse/Types/Serialization/NpHandle.cs +++ b/ProjectLighthouse/Types/Serialization/User/NpHandle.cs @@ -1,7 +1,7 @@ using System.ComponentModel; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.User; public class NpHandle : ILbpSerializable { diff --git a/ProjectLighthouse/Types/Serialization/UserListResponse.cs b/ProjectLighthouse/Types/Serialization/User/UserListResponse.cs similarity index 89% rename from ProjectLighthouse/Types/Serialization/UserListResponse.cs rename to ProjectLighthouse/Types/Serialization/User/UserListResponse.cs index e5a15630..32b8731d 100644 --- a/ProjectLighthouse/Types/Serialization/UserListResponse.cs +++ b/ProjectLighthouse/Types/Serialization/User/UserListResponse.cs @@ -2,7 +2,7 @@ using System.ComponentModel; using System.Xml.Serialization; -namespace LBPUnion.ProjectLighthouse.Types.Serialization; +namespace LBPUnion.ProjectLighthouse.Types.Serialization.User; public struct UserListResponse : ILbpSerializable, IHasCustomRoot { @@ -23,7 +23,7 @@ public struct UserListResponse : ILbpSerializable, IHasCustomRoot } [XmlIgnore] - public string RootTag { get; set; } + private string RootTag { get; set; } [XmlElement("user")] public List Users { get; set; }