From 41d2b5be7dacdeeaef4c0a90884fbd854e1ca5b0 Mon Sep 17 00:00:00 2001 From: Slendy Date: Thu, 7 Sep 2023 01:25:01 -0500 Subject: [PATCH] Prevent heart activity spam and fix photo grouping --- .../Activity/ActivityEventHandlerTests.cs | 227 +++++++++++++++++- .../Extensions/ActivityQueryExtensions.cs | 2 +- .../Activity/ActivityEntityEventHandler.cs | 47 ++++ 3 files changed, 274 insertions(+), 2 deletions(-) diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Activity/ActivityEventHandlerTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Activity/ActivityEventHandlerTests.cs index 6990c910..6f08b8b4 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Unit/Activity/ActivityEventHandlerTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Activity/ActivityEventHandlerTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Database; @@ -205,6 +206,55 @@ public class ActivityEventHandlerTests .FirstOrDefault(a => a.Type == EventType.HeartLevel && a.SlotId == 1)); } + [Fact] + public async Task HeartedLevel_InsertDuplicate_ShouldRemoveOldActivity() + { + ActivityEntityEventHandler eventHandler = new(); + DatabaseContext database = await MockHelper.GetTestDatabase(new List + { + new() + { + Username = "test", + UserId = 1, + }, + }); + + SlotEntity slot = new() + { + SlotId = 1, + CreatorId = 1, + }; + database.Slots.Add(slot); + + LevelActivityEntity levelActivity = new() + { + UserId = 1, + SlotId = 1, + Type = EventType.HeartLevel, + Timestamp = DateTime.MinValue, + }; + + database.Activities.Add(levelActivity); + + await database.SaveChangesAsync(); + + HeartedLevelEntity heartedLevel = new() + { + HeartedLevelId = 1, + UserId = 1, + SlotId = 1, + Slot = slot, + }; + + Assert.NotNull(database.Activities.OfType() + .FirstOrDefault(a => a.Type == EventType.HeartLevel && a.SlotId == 1 && a.Timestamp == DateTime.MinValue)); + + eventHandler.OnEntityInserted(database, heartedLevel); + + Assert.NotNull(database.Activities.OfType() + .FirstOrDefault(a => a.Type == EventType.HeartLevel && a.SlotId == 1 && a.Timestamp != DateTime.MinValue)); + } + [Fact] public async Task HeartedProfile_Insert_ShouldCreateUserActivity() { @@ -231,6 +281,46 @@ public class ActivityEventHandlerTests .FirstOrDefault(a => a.Type == EventType.HeartUser && a.TargetUserId == 1)); } + [Fact] + public async Task HeartedProfile_InsertDuplicate_ShouldRemoveOldActivity() + { + ActivityEntityEventHandler eventHandler = new(); + DatabaseContext database = await MockHelper.GetTestDatabase(new List + { + new() + { + Username = "test", + UserId = 1, + }, + }); + + UserActivityEntity userActivity = new() + { + UserId = 1, + TargetUserId = 1, + Type = EventType.HeartUser, + Timestamp = DateTime.MinValue, + }; + + database.Activities.Add(userActivity); + await database.SaveChangesAsync(); + + Assert.NotNull(database.Activities.OfType() + .FirstOrDefault(a => a.Type == EventType.HeartUser && a.TargetUserId == 1 && a.Timestamp == DateTime.MinValue)); + + HeartedProfileEntity heartedProfile = new() + { + HeartedProfileId = 1, + UserId = 1, + HeartedUserId = 1, + }; + + eventHandler.OnEntityInserted(database, heartedProfile); + + Assert.NotNull(database.Activities.OfType() + .FirstOrDefault(a => a.Type == EventType.HeartUser && a.TargetUserId == 1 && a.Timestamp != DateTime.MinValue)); + } + [Fact] public async Task HeartedPlaylist_Insert_ShouldCreatePlaylistActivity() { @@ -265,6 +355,54 @@ public class ActivityEventHandlerTests .FirstOrDefault(a => a.Type == EventType.HeartPlaylist && a.PlaylistId == 1)); } + [Fact] + public async Task HeartedPlaylist_InsertDuplicate_ShouldCreatePlaylistActivity() + { + ActivityEntityEventHandler eventHandler = new(); + DatabaseContext database = await MockHelper.GetTestDatabase(new List + { + new() + { + Username = "test", + UserId = 1, + }, + }); + + PlaylistEntity playlist = new() + { + PlaylistId = 1, + CreatorId = 1, + }; + database.Playlists.Add(playlist); + + PlaylistActivityEntity playlistActivity = new() + { + UserId = 1, + PlaylistId = 1, + Type = EventType.HeartPlaylist, + Timestamp = DateTime.MinValue, + }; + database.Activities.Add(playlistActivity); + + await database.SaveChangesAsync(); + + HeartedPlaylistEntity heartedPlaylist = new() + { + HeartedPlaylistId = 1, + UserId = 1, + PlaylistId = 1, + }; + + Assert.NotNull(database.Activities.OfType() + .FirstOrDefault(a => + a.Type == EventType.HeartPlaylist && a.PlaylistId == 1 && a.Timestamp == DateTime.MinValue)); + + eventHandler.OnEntityInserted(database, heartedPlaylist); + + Assert.NotNull(database.Activities.OfType() + .FirstOrDefault(a => a.Type == EventType.HeartPlaylist && a.PlaylistId == 1 && a.Timestamp != DateTime.MinValue)); + } + [Fact] public async Task VisitedLevel_Insert_ShouldCreateLevelActivity() { @@ -696,6 +834,52 @@ public class ActivityEventHandlerTests .FirstOrDefault(a => a.Type == EventType.UnheartLevel && a.SlotId == 1)); } + [Fact] + public async Task HeartedLevel_DeleteDuplicate_ShouldRemoveOldActivity() + { + ActivityEntityEventHandler eventHandler = new(); + DatabaseContext database = await MockHelper.GetTestDatabase(new List + { + new() + { + Username = "test", + UserId = 1, + }, + }); + + SlotEntity slot = new() + { + SlotId = 1, + CreatorId = 1, + }; + database.Slots.Add(slot); + + LevelActivityEntity levelActivity = new() + { + UserId = 1, + SlotId = 1, + Type = EventType.UnheartLevel, + Timestamp = DateTime.MinValue, + }; + + database.Activities.Add(levelActivity); + + HeartedLevelEntity heartedLevel = new() + { + HeartedLevelId = 1, + UserId = 1, + SlotId = 1, + }; + + database.HeartedLevels.Add(heartedLevel); + await database.SaveChangesAsync(); + + eventHandler.OnEntityDeleted(database, heartedLevel); + + Assert.NotNull(database.Activities.OfType() + .FirstOrDefault(a => a.Type == EventType.UnheartLevel && a.SlotId == 1 && a.Timestamp != DateTime.MinValue)); + } + [Fact] public async Task HeartedProfile_Delete_ShouldCreateLevelActivity() { @@ -731,5 +915,46 @@ public class ActivityEventHandlerTests Assert.NotNull(database.Activities.OfType() .FirstOrDefault(a => a.Type == EventType.UnheartUser && a.UserId == 1)); } + + [Fact] + public async Task HeartedProfile_DeleteDuplicate_ShouldCreateLevelActivity() + { + ActivityEntityEventHandler eventHandler = new(); + DatabaseContext database = await MockHelper.GetTestDatabase(new List + { + new() + { + Username = "test", + UserId = 1, + }, + }); + + UserActivityEntity userActivity = new() + { + UserId = 1, + TargetUserId = 1, + Type = EventType.UnheartUser, + Timestamp = DateTime.MinValue, + }; + database.Activities.Add(userActivity); + + HeartedProfileEntity heartedProfile = new() + { + HeartedProfileId = 1, + UserId = 1, + HeartedUserId = 1, + }; + + database.HeartedProfiles.Add(heartedProfile); + await database.SaveChangesAsync(); + + Assert.NotNull(database.Activities.OfType() + .FirstOrDefault(a => a.Type == EventType.UnheartUser && a.UserId == 1 && a.Timestamp == DateTime.MinValue)); + + eventHandler.OnEntityDeleted(database, heartedProfile); + + Assert.NotNull(database.Activities.OfType() + .FirstOrDefault(a => a.Type == EventType.UnheartUser && a.UserId == 1 && a.Timestamp != DateTime.MinValue)); + } #endregion } \ No newline at end of file diff --git a/ProjectLighthouse/Extensions/ActivityQueryExtensions.cs b/ProjectLighthouse/Extensions/ActivityQueryExtensions.cs index 92fbee97..67394c8c 100644 --- a/ProjectLighthouse/Extensions/ActivityQueryExtensions.cs +++ b/ProjectLighthouse/Extensions/ActivityQueryExtensions.cs @@ -118,7 +118,7 @@ public static class ActivityQueryExtensions ? ((UserActivityEntity)a).TargetUserId : a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Profile ? ((CommentActivityEntity)a).Comment.TargetUserId - : a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.SlotId != 0 + : a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.SlotId == 0 ? ((PhotoActivityEntity)a).Photo.CreatorId : 0, TargetPlaylistId = a is PlaylistActivityEntity || a is PlaylistWithSlotActivityEntity diff --git a/ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs b/ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs index 29d7f9a8..2342c566 100644 --- a/ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs +++ b/ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs @@ -1,7 +1,9 @@ #nullable enable using System; using System.Linq; +using System.Linq.Expressions; using LBPUnion.ProjectLighthouse.Database; +using LBPUnion.ProjectLighthouse.Extensions; using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Types.Entities.Activity; using LBPUnion.ProjectLighthouse.Types.Entities.Interaction; @@ -144,12 +146,57 @@ public class ActivityEntityEventHandler : IEntityEventHandler InsertActivity(database, activity); } + private static void RemoveDuplicateEvents(DatabaseContext database, ActivityEntity activity) + { + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (activity.Type) + { + case EventType.HeartLevel: + case EventType.UnheartLevel: + { + if (activity is not LevelActivityEntity levelActivity) break; + + DeleteActivity(a => a.TargetSlotId == levelActivity.SlotId); + break; + } + case EventType.HeartUser: + case EventType.UnheartUser: + { + if (activity is not UserActivityEntity userActivity) break; + + DeleteActivity(a => a.TargetUserId == userActivity.TargetUserId); + break; + } + case EventType.HeartPlaylist: + { + if (activity is not PlaylistActivityEntity playlistActivity) break; + + DeleteActivity(a => a.TargetPlaylistId == playlistActivity.PlaylistId); + break; + } + } + + return; + + void DeleteActivity(Expression> predicate) + { + database.Activities.ToActivityDto() + .Where(a => a.Activity.UserId == activity.UserId) + .Where(a => a.Activity.Type == activity.Type) + .Where(predicate) + .Select(a => a.Activity) + .ExecuteDelete(); + } + } + private static void InsertActivity(DatabaseContext database, ActivityEntity? activity) { if (activity == null) return; Logger.Debug("Inserting activity: " + activity.GetType().Name, LogArea.Activity); + RemoveDuplicateEvents(database, activity); + activity.Timestamp = DateTime.UtcNow; database.Activities.Add(activity); database.SaveChanges();