diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 760f6172..9e01b2fa 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "7.0.13", + "version": "8.0.0", "commands": [ "dotnet-ef" ] diff --git a/ProjectLighthouse.Servers.GameServer/Controllers/ActivityController.cs b/ProjectLighthouse.Servers.GameServer/Controllers/ActivityController.cs index 792c6711..fb511289 100644 --- a/ProjectLighthouse.Servers.GameServer/Controllers/ActivityController.cs +++ b/ProjectLighthouse.Servers.GameServer/Controllers/ActivityController.cs @@ -56,7 +56,7 @@ public class ActivityController : ControllerBase .ToListAsync(); List? friendIds = UserFriendStore.GetUserFriendData(token.UserId)?.FriendIds; - friendIds ??= new List(); + friendIds ??= []; // This is how lbp3 does its filtering GameStreamFilter? filter = await this.DeserializeBody(); @@ -89,7 +89,7 @@ public class ActivityController : ControllerBase predicate = predicate.Or(dto => dto.TargetSlotCreatorId == token.UserId); } - List includedUserIds = new(); + List includedUserIds = []; if (!excludeFriends) { @@ -168,7 +168,7 @@ public class ActivityController : ControllerBase private static DateTime GetOldestTime (IReadOnlyCollection> groups, DateTime defaultTimestamp) => - groups.Any() + groups.Count != 0 ? groups.Min(g => g.MinBy(a => a.Activity.Timestamp)?.Activity.Timestamp ?? defaultTimestamp) : defaultTimestamp; @@ -247,7 +247,7 @@ public class ActivityController : ControllerBase { GameTokenEntity token = this.GetToken(); - if (token.GameVersion == GameVersion.LittleBigPlanet1) return this.NotFound(); + if (token.GameVersion is GameVersion.LittleBigPlanet1 or GameVersion.LittleBigPlanetPSP) return this.NotFound(); IQueryable activityEvents = await this.GetFilters(this.database.Activities.ToActivityDto(true), token, @@ -305,10 +305,11 @@ public class ActivityController : ControllerBase { GameTokenEntity token = this.GetToken(); - if (token.GameVersion == GameVersion.LittleBigPlanet1) return this.NotFound(); + if (token.GameVersion is GameVersion.LittleBigPlanet1 or GameVersion.LittleBigPlanetPSP) return this.NotFound(); if ((SlotHelper.IsTypeInvalid(slotType) || slotId == 0) == (username == null)) return this.BadRequest(); + // User and Level activity will never contain news posts or MM pick events. IQueryable activityQuery = this.database.Activities.ToActivityDto() .Where(a => a.Activity.Type != EventType.NewsPost && a.Activity.Type != EventType.MMPickLevel); @@ -343,6 +344,8 @@ public class ActivityController : ControllerBase List outerGroups = groups.ToOuterActivityGroups(); + PrintOuterGroups(outerGroups); + long oldestTimestamp = GetOldestTime(groups, times.Start).ToUnixTimeMilliseconds(); await this.CacheEntities(outerGroups); diff --git a/ProjectLighthouse.Tests.GameApiTests/Unit/Activity/ActivityGroupingTests.cs b/ProjectLighthouse.Tests.GameApiTests/Unit/Activity/ActivityGroupingTests.cs index 617c0caa..8843091c 100644 --- a/ProjectLighthouse.Tests.GameApiTests/Unit/Activity/ActivityGroupingTests.cs +++ b/ProjectLighthouse.Tests.GameApiTests/Unit/Activity/ActivityGroupingTests.cs @@ -1,31 +1,345 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Extensions; +using LBPUnion.ProjectLighthouse.Tests.Helpers; 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.Website; +using LBPUnion.ProjectLighthouse.Types.Users; +using Microsoft.EntityFrameworkCore; using Xunit; namespace ProjectLighthouse.Tests.GameApiTests.Unit.Activity; +[Trait("Category", "Unit")] public class ActivityGroupingTests { [Fact] - public void ActivityGroupingTest() + public void ToOuterActivityGroups_ShouldCreateGroupPerObject_WhenGroupedBy_ObjectThenActor() { - List activities = new() - { - new ActivityDto + List activities = [ + new LevelActivityEntity { - TargetPlaylistId = 1, - Activity = new ActivityEntity(), + UserId = 1, + SlotId = 1, + Slot = new SlotEntity + { + GameVersion = GameVersion.LittleBigPlanet2, + }, + Timestamp = DateTime.Now, + Type = EventType.PlayLevel, }, - }; - List groups = activities.AsQueryable().ToActivityGroups().ToList().ToOuterActivityGroups(); + new LevelActivityEntity + { + UserId = 1, + SlotId = 1, + Slot = new SlotEntity + { + GameVersion = GameVersion.LittleBigPlanet2, + }, + Timestamp = DateTime.Now, + Type = EventType.ReviewLevel, + }, + new LevelActivityEntity + { + UserId = 2, + SlotId = 1, + Slot = new SlotEntity + { + GameVersion = GameVersion.LittleBigPlanet2, + }, + Timestamp = DateTime.Now, + Type = EventType.PlayLevel, + }, + new UserActivityEntity + { + TargetUserId = 2, + UserId = 1, + Type = EventType.HeartUser, + Timestamp = DateTime.Now, + }, + new UserActivityEntity + { + TargetUserId = 2, + UserId = 1, + Type = EventType.CommentOnUser, + Timestamp = DateTime.Now, + }, + new UserActivityEntity + { + TargetUserId = 1, + UserId = 2, + Type = EventType.HeartUser, + Timestamp = DateTime.Now, + }, + new UserActivityEntity + { + TargetUserId = 1, + UserId = 2, + Type = EventType.CommentOnUser, + Timestamp = DateTime.Now, + }, + ]; + + //TODO: fix test + List groups = activities.ToActivityDto().AsQueryable().ToActivityGroups().ToList().ToOuterActivityGroups(); Assert.NotNull(groups); Assert.Single(groups); - OuterActivityGroup groupEntry = groups.First(); - - Assert.Equal(ActivityGroupType.Playlist, groupEntry.Key.GroupType); - Assert.Equal(1, groupEntry.Key.TargetId); + OuterActivityGroup outerGroup = groups.First(); + + Assert.Equal(ActivityGroupType.Level, outerGroup.Key.GroupType); + Assert.Equal(1, outerGroup.Key.TargetSlotId); + + IGrouping? firstGroup = outerGroup.Groups.First(); + IGrouping? secondGroup = outerGroup.Groups.Last(); + + Assert.NotNull(secondGroup); + Assert.Equal(ActivityGroupType.User, secondGroup.Key.Type); + Assert.Equal(1, secondGroup.Key.TargetId); // user group should have the user id + Assert.Equal(1, secondGroup.ToList()[0].TargetSlotId); // events in user group should have t + Assert.Equal(1, secondGroup.ToList()[1].TargetSlotId); + + Assert.NotNull(firstGroup); + Assert.Equal(ActivityGroupType.User, firstGroup.Key.Type); + Assert.Equal(2, firstGroup.Key.TargetId); + Assert.Equal(1, firstGroup.ToList()[0].TargetSlotId); + } + + [Fact] + public void ToOuterActivityGroups_ShouldCreateGroupPerObject_WhenGroupedBy_ActorThenObject() + { + List activities = [ + new LevelActivityEntity + { + UserId = 1, + SlotId = 1, + Slot = new SlotEntity + { + GameVersion = GameVersion.LittleBigPlanet2, + }, + Timestamp = DateTime.Now, + Type = EventType.PlayLevel, + }, + new LevelActivityEntity + { + UserId = 1, + SlotId = 1, + Slot = new SlotEntity + { + GameVersion = GameVersion.LittleBigPlanet2, + }, + Timestamp = DateTime.Now, + Type = EventType.ReviewLevel, + }, + new LevelActivityEntity + { + UserId = 2, + SlotId = 1, + Slot = new SlotEntity + { + GameVersion = GameVersion.LittleBigPlanet2, + }, + Timestamp = DateTime.Now, + Type = EventType.PlayLevel, + }, + new UserActivityEntity + { + TargetUserId = 2, + UserId = 1, + Type = EventType.HeartUser, + Timestamp = DateTime.Now, + }, + new UserActivityEntity + { + TargetUserId = 2, + UserId = 1, + Type = EventType.CommentOnUser, + Timestamp = DateTime.Now, + }, + new UserActivityEntity + { + TargetUserId = 1, + UserId = 2, + Type = EventType.HeartUser, + Timestamp = DateTime.Now, + }, + new UserActivityEntity + { + TargetUserId = 1, + UserId = 2, + Type = EventType.CommentOnUser, + Timestamp = DateTime.Now, + }, + ]; + List groups = activities.ToActivityDto() + .AsQueryable() + .ToActivityGroups(true) + .ToList() + .ToOuterActivityGroups(true); + //TODO: fix test + Assert.Multiple(() => + { + Assert.NotNull(groups); + Assert.Equal(2, groups.Count); + OuterActivityGroup firstUserGroup = groups.FirstOrDefault(g => g.Key.UserId == 1); + OuterActivityGroup secondUserGroup = groups.FirstOrDefault(g => g.Key.UserId == 2); + Assert.NotNull(firstUserGroup.Groups); + Assert.NotNull(secondUserGroup.Groups); + + Assert.Equal(ActivityGroupType.User, firstUserGroup.Key.GroupType); + Assert.Equal(ActivityGroupType.User, secondUserGroup.Key.GroupType); + + Assert.Single(firstUserGroup.Groups); + Assert.Single(secondUserGroup.Groups); + + Assert.Equal(2, firstUserGroup.Groups.ToList()[0].Count()); + Assert.Single(secondUserGroup.Groups.ToList()[0]); + + // Assert.Equal(ActivityGroupType.Level, outerGroup.Key.GroupType); + // Assert.Equal(1, outerGroup.Key.TargetSlotId); + // + // IGrouping? firstGroup = outerGroup.Groups.First(); + // IGrouping? secondGroup = outerGroup.Groups.Last(); + // + // Assert.NotNull(secondGroup); + // Assert.Equal(ActivityGroupType.User, secondGroup.Key.Type); + // Assert.Equal(1, secondGroup.Key.TargetId); // user group should have the user id + // Assert.Equal(1, secondGroup.ToList()[0].TargetSlotId); // events in user group should have t + // Assert.Equal(1, secondGroup.ToList()[1].TargetSlotId); + // + // Assert.NotNull(firstGroup); + // Assert.Equal(ActivityGroupType.User, firstGroup.Key.Type); + // Assert.Equal(2, firstGroup.Key.TargetId); + // Assert.Equal(1, firstGroup.ToList()[0].TargetSlotId); + }); + } + + [Fact] + public async Task ToActivityDtoTest() + { + DatabaseContext db = await MockHelper.GetTestDatabase(); + db.Slots.Add(new SlotEntity + { + SlotId = 1, + CreatorId = 1, + GameVersion = GameVersion.LittleBigPlanet2, + }); + db.Slots.Add(new SlotEntity + { + SlotId = 2, + CreatorId = 1, + GameVersion = GameVersion.LittleBigPlanet2, + TeamPickTime = 1, + }); + db.Reviews.Add(new ReviewEntity + { + Timestamp = DateTime.Now.ToUnixTimeMilliseconds(), + SlotId = 1, + ReviewerId = 1, + ReviewId = 1, + }); + db.Comments.Add(new CommentEntity + { + TargetSlotId = 1, + PosterUserId = 1, + Message = "comment on level test", + CommentId = 1, + }); + db.Comments.Add(new CommentEntity + { + TargetUserId = 1, + PosterUserId = 1, + Message = "comment on user test", + CommentId = 2, + }); + db.WebsiteAnnouncements.Add(new WebsiteAnnouncementEntity + { + PublisherId = 1, + AnnouncementId = 1, + }); + db.Playlists.Add(new PlaylistEntity + { + PlaylistId = 1, + CreatorId = 1, + }); + db.Activities.Add(new LevelActivityEntity + { + Timestamp = DateTime.Now, + SlotId = 1, + Type = EventType.PlayLevel, + UserId = 1, + }); + db.Activities.Add(new ReviewActivityEntity + { + Timestamp = DateTime.Now, + SlotId = 1, + Type = EventType.ReviewLevel, + ReviewId = 1, + UserId = 1, + }); + db.Activities.Add(new UserCommentActivityEntity + { + Timestamp = DateTime.Now, + Type = EventType.CommentOnUser, + UserId = 1, + TargetUserId = 1, + CommentId = 2, + }); + db.Activities.Add(new LevelCommentActivityEntity + { + Timestamp = DateTime.Now, + Type = EventType.CommentOnLevel, + UserId = 1, + SlotId = 1, + CommentId = 1, + }); + db.Activities.Add(new NewsActivityEntity + { + Type = EventType.NewsPost, + NewsId = 1, + UserId = 1, + }); + db.Activities.Add(new PlaylistActivityEntity + { + Type = EventType.CreatePlaylist, + PlaylistId = 1, + UserId = 1, + }); + db.Activities.Add(new LevelActivityEntity + { + Type = EventType.MMPickLevel, + SlotId = 2, + UserId = 1, + }); + await db.SaveChangesAsync(); + + var sql = db.Activities.ToActivityDto().ToQueryString(); + + List resultDto = await db.Activities.ToActivityDto(includeSlotCreator: true, includeTeamPick: true).ToListAsync(); + + Assert.Equal(2, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.MMPickLevel)?.TargetTeamPickId); + Assert.Equal(2, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.MMPickLevel)?.TargetSlotId); + Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.MMPickLevel)?.TargetSlotCreatorId); + + Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.CreatePlaylist)?.TargetPlaylistId); + + Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.NewsPost)?.TargetNewsId); + + Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.CommentOnUser)?.TargetUserId); + + Assert.Null(resultDto.FirstOrDefault(a => a.Activity.Type == EventType.CommentOnLevel)?.TargetTeamPickId); + Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.CommentOnLevel)?.TargetSlotId); + Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.CommentOnLevel)?.TargetSlotCreatorId); + + Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.ReviewLevel)?.TargetSlotId); + Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.ReviewLevel)?.TargetSlotCreatorId); + + Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.PlayLevel)?.TargetSlotId); + Assert.Equal(1, resultDto.FirstOrDefault(a => a.Activity.Type == EventType.PlayLevel)?.TargetSlotCreatorId); } } \ No newline at end of file diff --git a/ProjectLighthouse/Database/ActivityInterceptor.cs b/ProjectLighthouse/Database/ActivityInterceptor.cs index ca5d66f5..fb364d95 100644 --- a/ProjectLighthouse/Database/ActivityInterceptor.cs +++ b/ProjectLighthouse/Database/ActivityInterceptor.cs @@ -93,7 +93,7 @@ public class ActivityInterceptor : SaveChangesInterceptor { if (eventData.Context is not DatabaseContext context) return; - HashSet entities = new(); + HashSet entities = []; List entries = context.ChangeTracker.Entries().ToList(); diff --git a/ProjectLighthouse/Database/DatabaseContext.cs b/ProjectLighthouse/Database/DatabaseContext.cs index 6fc43ccf..6c981b25 100644 --- a/ProjectLighthouse/Database/DatabaseContext.cs +++ b/ProjectLighthouse/Database/DatabaseContext.cs @@ -109,13 +109,15 @@ public partial class DatabaseContext : DbContext protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().UseTphMappingStrategy(); - modelBuilder.Entity().UseTphMappingStrategy(); + modelBuilder.Entity().UseTphMappingStrategy(); + modelBuilder.Entity().UseTphMappingStrategy(); modelBuilder.Entity().UseTphMappingStrategy(); modelBuilder.Entity().UseTphMappingStrategy(); 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); diff --git a/ProjectLighthouse/Extensions/ActivityQueryExtensions.cs b/ProjectLighthouse/Extensions/ActivityQueryExtensions.cs index 67394c8c..0697ec5e 100644 --- a/ProjectLighthouse/Extensions/ActivityQueryExtensions.cs +++ b/ProjectLighthouse/Extensions/ActivityQueryExtensions.cs @@ -2,7 +2,6 @@ using System.Linq; using LBPUnion.ProjectLighthouse.Types.Activity; using LBPUnion.ProjectLighthouse.Types.Entities.Activity; -using LBPUnion.ProjectLighthouse.Types.Entities.Profile; namespace LBPUnion.ProjectLighthouse.Extensions; @@ -10,7 +9,7 @@ public static class ActivityQueryExtensions { public static List GetIds(this IReadOnlyCollection groups, ActivityGroupType type) { - List ids = new(); + List ids = []; // Add outer group ids ids.AddRange(groups.Where(g => g.Key.GroupType == type) .Where(g => g.Key.TargetId != 0) @@ -29,6 +28,12 @@ public static class ActivityQueryExtensions return ids.Distinct().ToList(); } + /// + /// Turns a list of into a group based on its timestamp + /// + /// An to group + /// Whether or not the groups should be created based on the initiator of the event or the target of the event + /// The transformed query containing groups of public static IQueryable> ToActivityGroups (this IQueryable activityQuery, bool groupByActor = false) => groupByActor @@ -59,74 +64,65 @@ public static class ActivityQueryExtensions Groups = g.OrderByDescending(a => a.Activity.Timestamp) .GroupBy(gr => new InnerActivityGroup { - Type = groupByActor ? gr.GroupType : gr.GroupType != ActivityGroupType.News ? ActivityGroupType.User : ActivityGroupType.News, + Type = groupByActor + ? gr.GroupType + : gr.GroupType != ActivityGroupType.News + ? ActivityGroupType.User + : ActivityGroupType.News, UserId = gr.Activity.UserId, - TargetId = groupByActor ? gr.TargetId : gr.GroupType != ActivityGroupType.News ? gr.Activity.UserId : gr.TargetNewsId ?? 0, + TargetId = groupByActor + ? gr.TargetId + : gr.GroupType != ActivityGroupType.News + ? gr.Activity.UserId + : gr.TargetNewsId ?? 0, }) .ToList(), }) .ToList(); - // WARNING - To the next person who tries to improve this code: As of writing this, it's not possible - // to build a pattern matching switch statement with expression trees. so the only other option - // is to basically rewrite this nested ternary mess with expression trees which isn't much better - // The resulting SQL generated by EntityFramework uses a CASE statement which is probably fine - // TOTAL HOURS WASTED: 3 + /// + /// Converts an <> into an <> for grouping. + /// + /// The activity query to be converted. + /// Whether or not the field should be included. + /// Whether or not the field should be included. + /// The converted <> public static IQueryable ToActivityDto (this IQueryable activityQuery, bool includeSlotCreator = false, bool includeTeamPick = false) { return activityQuery.Select(a => new ActivityDto { Activity = a, - TargetSlotId = a is LevelActivityEntity - ? ((LevelActivityEntity)a).SlotId - : a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.SlotId != 0 - ? ((PhotoActivityEntity)a).Photo.SlotId - : a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Level - ? ((CommentActivityEntity)a).Comment.TargetSlotId - : a is ScoreActivityEntity - ? ((ScoreActivityEntity)a).Score.SlotId - : a is ReviewActivityEntity - ? ((ReviewActivityEntity)a).Review.SlotId - : 0, - TargetSlotGameVersion = a is LevelActivityEntity - ? ((LevelActivityEntity)a).Slot.GameVersion - : a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.SlotId != 0 - ? ((PhotoActivityEntity)a).Photo.Slot.GameVersion - : a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Level - ? ((CommentActivityEntity)a).Comment.TargetSlot.GameVersion - : a is ScoreActivityEntity - ? ((ScoreActivityEntity)a).Score.Slot.GameVersion - : a is ReviewActivityEntity - ? ((ReviewActivityEntity)a).Review.Slot.GameVersion - : 0, - TargetSlotCreatorId = includeSlotCreator - ? 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.TargetSlot.CreatorId - : a is ScoreActivityEntity - ? ((ScoreActivityEntity)a).Score.Slot.CreatorId - : a is ReviewActivityEntity - ? ((ReviewActivityEntity)a).Review.Slot!.CreatorId - : 0 - : 0, + TargetSlotId = (a as LevelActivityEntity).SlotId, + TargetSlotGameVersion = (a as LevelActivityEntity).Slot.GameVersion, + TargetSlotCreatorId = includeSlotCreator ? (a as LevelActivityEntity).Slot.CreatorId : null, + TargetUserId = (a as UserActivityEntity).TargetUserId, + TargetNewsId = (a as NewsActivityEntity).NewsId, + TargetPlaylistId = (a as PlaylistActivityEntity).PlaylistId, + TargetTeamPickId = + includeTeamPick && a.Type == EventType.MMPickLevel ? (a as LevelActivityEntity).SlotId : null, }); + } - TargetUserId = a is UserActivityEntity - ? ((UserActivityEntity)a).TargetUserId - : a is CommentActivityEntity && ((CommentActivityEntity)a).Comment.Type == CommentType.Profile - ? ((CommentActivityEntity)a).Comment.TargetUserId - : a is PhotoActivityEntity && ((PhotoActivityEntity)a).Photo.SlotId == 0 - ? ((PhotoActivityEntity)a).Photo.CreatorId - : 0, - TargetPlaylistId = a is PlaylistActivityEntity || a is PlaylistWithSlotActivityEntity - ? ((PlaylistActivityEntity)a).PlaylistId - : 0, - TargetNewsId = a is NewsActivityEntity ? ((NewsActivityEntity)a).NewsId : 0, - TargetTeamPickId = includeTeamPick - ? a.Type == EventType.MMPickLevel && a is LevelActivityEntity ? ((LevelActivityEntity)a).SlotId : 0 - : 0, }); + /// + /// Converts an IEnumerable<> into an IEnumerable<> for grouping. + /// + /// The activity query to be converted. + /// Whether or not the field should be included. + /// Whether or not the field should be included. + /// The converted IEnumerable<> + public static IEnumerable ToActivityDto + (this IEnumerable activityEnumerable, bool includeSlotCreator = false, bool includeTeamPick = false) + { + return activityEnumerable.Select(a => new ActivityDto + { + Activity = a, + TargetSlotId = (a as LevelActivityEntity)?.SlotId, + TargetSlotGameVersion = (a as LevelActivityEntity)?.Slot.GameVersion, + TargetSlotCreatorId = includeSlotCreator ? (a as LevelActivityEntity)?.Slot.CreatorId : null, + TargetUserId = (a as UserActivityEntity)?.TargetUserId, + TargetNewsId = (a as NewsActivityEntity)?.NewsId, + TargetPlaylistId = (a as PlaylistActivityEntity)?.PlaylistId, + TargetTeamPickId = + includeTeamPick && a.Type == EventType.MMPickLevel ? (a as LevelActivityEntity)?.SlotId : null, }); } } \ No newline at end of file diff --git a/ProjectLighthouse/Migrations/20240120214525_InitialActivity.cs b/ProjectLighthouse/Migrations/20240325034658_InitialActivity.cs similarity index 97% rename from ProjectLighthouse/Migrations/20240120214525_InitialActivity.cs rename to ProjectLighthouse/Migrations/20240325034658_InitialActivity.cs index 9f4d01a4..62ea0bd4 100644 --- a/ProjectLighthouse/Migrations/20240120214525_InitialActivity.cs +++ b/ProjectLighthouse/Migrations/20240325034658_InitialActivity.cs @@ -6,10 +6,10 @@ using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace ProjectLighthouse.Migrations +namespace LBPUnion.ProjectLighthouse.Migrations { [DbContext(typeof(DatabaseContext))] - [Migration("20240120214525_InitialActivity")] + [Migration("20240325034658_InitialActivity")] public partial class InitialActivity : Migration { /// @@ -24,12 +24,12 @@ namespace ProjectLighthouse.Migrations Timestamp = table.Column(type: "datetime(6)", nullable: false), UserId = table.Column(type: "int", nullable: false), Type = table.Column(type: "int", nullable: false), - Discriminator = table.Column(type: "longtext", nullable: false) + Discriminator = table.Column(type: "varchar(34)", maxLength: 34, nullable: false) .Annotation("MySql:CharSet", "utf8mb4"), - CommentId = table.Column(type: "int", nullable: true), SlotId = table.Column(type: "int", nullable: true), - NewsId = table.Column(type: "int", nullable: true), + CommentId = table.Column(type: "int", nullable: true), PhotoId = table.Column(type: "int", nullable: true), + NewsId = table.Column(type: "int", nullable: true), PlaylistId = table.Column(type: "int", nullable: true), ReviewId = table.Column(type: "int", nullable: true), ScoreId = table.Column(type: "int", nullable: true), diff --git a/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs index 5c33884b..7f069036 100644 --- a/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseContextModelSnapshot.cs @@ -27,7 +27,8 @@ namespace ProjectLighthouse.Migrations b.Property("Discriminator") .IsRequired() - .HasColumnType("longtext"); + .HasMaxLength(34) + .HasColumnType("varchar(34)"); b.Property("Timestamp") .HasColumnType("datetime(6)"); @@ -1123,18 +1124,6 @@ namespace ProjectLighthouse.Migrations b.ToTable("WebsiteAnnouncements"); }); - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.CommentActivityEntity", b => - { - b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity"); - - b.Property("CommentId") - .HasColumnType("int"); - - b.HasIndex("CommentId"); - - b.HasDiscriminator().HasValue("CommentActivityEntity"); - }); - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.LevelActivityEntity", b => { b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity"); @@ -1149,6 +1138,46 @@ namespace ProjectLighthouse.Migrations b.HasDiscriminator().HasValue("LevelActivityEntity"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.LevelCommentActivityEntity", b => + { + b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity"); + + b.Property("CommentId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("int"); + + b.Property("SlotId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("int") + .HasColumnName("SlotId"); + + b.HasIndex("CommentId"); + + b.HasIndex("SlotId"); + + b.HasDiscriminator().HasValue("LevelCommentActivityEntity"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.LevelPhotoActivity", b => + { + b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity"); + + b.Property("PhotoId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("int"); + + b.Property("SlotId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("int") + .HasColumnName("SlotId"); + + b.HasIndex("PhotoId"); + + b.HasIndex("SlotId"); + + b.HasDiscriminator().HasValue("LevelPhotoActivity"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.NewsActivityEntity", b => { b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity"); @@ -1161,18 +1190,6 @@ namespace ProjectLighthouse.Migrations b.HasDiscriminator().HasValue("NewsActivityEntity"); }); - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.PhotoActivityEntity", b => - { - b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity"); - - b.Property("PhotoId") - .HasColumnType("int"); - - b.HasIndex("PhotoId"); - - b.HasDiscriminator().HasValue("PhotoActivityEntity"); - }); - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.PlaylistActivityEntity", b => { b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity"); @@ -1213,8 +1230,15 @@ namespace ProjectLighthouse.Migrations b.Property("ReviewId") .HasColumnType("int"); + b.Property("SlotId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("int") + .HasColumnName("SlotId"); + b.HasIndex("ReviewId"); + b.HasIndex("SlotId"); + b.HasDiscriminator().HasValue("ReviewActivityEntity"); }); @@ -1225,8 +1249,15 @@ namespace ProjectLighthouse.Migrations b.Property("ScoreId") .HasColumnType("int"); + b.Property("SlotId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("int") + .HasColumnName("SlotId"); + b.HasIndex("ScoreId"); + b.HasIndex("SlotId"); + b.HasDiscriminator().HasValue("ScoreActivityEntity"); }); @@ -1235,13 +1266,55 @@ namespace ProjectLighthouse.Migrations b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity"); b.Property("TargetUserId") - .HasColumnType("int"); + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("int") + .HasColumnName("TargetUserId"); b.HasIndex("TargetUserId"); b.HasDiscriminator().HasValue("UserActivityEntity"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.UserCommentActivityEntity", b => + { + b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity"); + + b.Property("CommentId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("int"); + + b.Property("TargetUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("int") + .HasColumnName("TargetUserId"); + + b.HasIndex("CommentId"); + + b.HasIndex("TargetUserId"); + + b.HasDiscriminator().HasValue("UserCommentActivityEntity"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.UserPhotoActivity", b => + { + b.HasBaseType("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity"); + + b.Property("PhotoId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("int"); + + b.Property("TargetUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("int") + .HasColumnName("TargetUserId"); + + b.HasIndex("PhotoId"); + + b.HasIndex("TargetUserId"); + + b.HasDiscriminator().HasValue("UserPhotoActivity"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ActivityEntity", b => { b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.UserEntity", "User") @@ -1646,17 +1719,6 @@ namespace ProjectLighthouse.Migrations b.Navigation("Publisher"); }); - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.CommentActivityEntity", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.CommentEntity", "Comment") - .WithMany() - .HasForeignKey("CommentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Comment"); - }); - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.LevelActivityEntity", b => { b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity", "Slot") @@ -1668,6 +1730,44 @@ namespace ProjectLighthouse.Migrations b.Navigation("Slot"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.LevelCommentActivityEntity", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.CommentEntity", "Comment") + .WithMany() + .HasForeignKey("CommentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Comment"); + + b.Navigation("Slot"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.LevelPhotoActivity", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.PhotoEntity", "Photo") + .WithMany() + .HasForeignKey("PhotoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Photo"); + + b.Navigation("Slot"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.NewsActivityEntity", b => { b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Website.WebsiteAnnouncementEntity", "News") @@ -1679,17 +1779,6 @@ namespace ProjectLighthouse.Migrations b.Navigation("News"); }); - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.PhotoActivityEntity", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.PhotoEntity", "Photo") - .WithMany() - .HasForeignKey("PhotoId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Photo"); - }); - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.PlaylistActivityEntity", b => { b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.PlaylistEntity", "Playlist") @@ -1720,7 +1809,15 @@ namespace ProjectLighthouse.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Review"); + + b.Navigation("Slot"); }); modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.ScoreActivityEntity", b => @@ -1731,7 +1828,15 @@ namespace ProjectLighthouse.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Level.SlotEntity", "Slot") + .WithMany() + .HasForeignKey("SlotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Score"); + + b.Navigation("Slot"); }); modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.UserActivityEntity", b => @@ -1745,6 +1850,44 @@ namespace ProjectLighthouse.Migrations b.Navigation("TargetUser"); }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.UserCommentActivityEntity", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.CommentEntity", "Comment") + .WithMany() + .HasForeignKey("CommentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.UserEntity", "TargetUser") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Comment"); + + b.Navigation("TargetUser"); + }); + + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Activity.UserPhotoActivity", b => + { + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.PhotoEntity", "Photo") + .WithMany() + .HasForeignKey("PhotoId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LBPUnion.ProjectLighthouse.Types.Entities.Profile.UserEntity", "TargetUser") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Photo"); + + b.Navigation("TargetUser"); + }); + modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Entities.Profile.PhotoEntity", b => { b.Navigation("PhotoSubjects"); diff --git a/ProjectLighthouse/Types/Activity/ActivityDto.cs b/ProjectLighthouse/Types/Activity/ActivityDto.cs index d813f76d..87759369 100644 --- a/ProjectLighthouse/Types/Activity/ActivityDto.cs +++ b/ProjectLighthouse/Types/Activity/ActivityDto.cs @@ -25,11 +25,11 @@ public class ActivityDto }; public ActivityGroupType GroupType => - this.TargetSlotId != 0 + this.TargetSlotId != null ? ActivityGroupType.Level - : this.TargetUserId != 0 + : this.TargetUserId != null ? ActivityGroupType.User - : this.TargetPlaylistId != 0 + : this.TargetPlaylistId != null ? ActivityGroupType.Playlist : ActivityGroupType.News; } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs b/ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs index 46f5ebb2..a2b520e6 100644 --- a/ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs +++ b/ProjectLighthouse/Types/Activity/ActivityEntityEventHandler.cs @@ -41,40 +41,44 @@ public class ActivityEntityEventHandler : IEntityEventHandler { CommentType.Level => comment.TargetSlot?.Type switch { - SlotType.User => new CommentActivityEntity + SlotType.User => new LevelCommentActivityEntity { Type = EventType.CommentOnLevel, CommentId = comment.CommentId, UserId = comment.PosterUserId, + SlotId = comment.TargetSlotId ?? throw new NullReferenceException("SlotId in Level comment is null, this shouldn't happen."), }, _ => null, }, - CommentType.Profile => new CommentActivityEntity + CommentType.Profile => new UserCommentActivityEntity() { Type = EventType.CommentOnUser, CommentId = comment.CommentId, UserId = comment.PosterUserId, + TargetUserId = comment.TargetUserId ?? throw new NullReferenceException("TargetUserId in User comment is null, this shouldn't happen."), }, _ => null, }, PhotoEntity photo => photo.SlotId switch { // Photos without levels - null => new PhotoActivityEntity + null => new UserPhotoActivity { Type = EventType.UploadPhoto, PhotoId = photo.PhotoId, UserId = photo.CreatorId, + TargetUserId = photo.CreatorId, }, _ => photo.Slot?.Type switch { SlotType.Developer => null, // Non-story levels (moon, pod, etc) - _ => new PhotoActivityEntity + _ => new LevelPhotoActivity { Type = EventType.UploadPhoto, PhotoId = photo.PhotoId, UserId = photo.CreatorId, + SlotId = photo.SlotId ?? throw new NullReferenceException("SlotId in Photo is null"), }, }, }, @@ -86,6 +90,7 @@ public class ActivityEntityEventHandler : IEntityEventHandler Type = EventType.Score, ScoreId = score.ScoreId, UserId = score.UserId, + SlotId = score.SlotId, }, _ => null, }, @@ -122,6 +127,7 @@ public class ActivityEntityEventHandler : IEntityEventHandler Type = EventType.ReviewLevel, ReviewId = review.ReviewId, UserId = review.ReviewerId, + SlotId = review.SlotId, }, RatedLevelEntity ratedLevel => new LevelActivityEntity { diff --git a/ProjectLighthouse/Types/Activity/EventType.cs b/ProjectLighthouse/Types/Activity/EventType.cs index f70100cd..c497b047 100644 --- a/ProjectLighthouse/Types/Activity/EventType.cs +++ b/ProjectLighthouse/Types/Activity/EventType.cs @@ -3,7 +3,12 @@ namespace LBPUnion.ProjectLighthouse.Types.Activity; /// -/// UnheartLevel, UnheartUser, DeleteLevelComment, and UnpublishLevel don't actually do anything +/// An enum of all possible event types that LBP recognizes in Recent Activity +/// +/// +/// , , , are ignored by the game +/// +/// /// public enum EventType { @@ -61,12 +66,21 @@ public enum EventType [XmlEnum("comment_on_user")] CommentOnUser = 17, + /// + /// This event is only used in LBP3 + /// > [XmlEnum("create_playlist")] CreatePlaylist = 18, + /// + /// This event is only used in LBP3 + /// > [XmlEnum("heart_playlist")] HeartPlaylist = 19, + /// + /// This event is only used in LBP3 + /// > [XmlEnum("add_level_to_playlist")] AddLevelToPlaylist = 20, } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Entities/Activity/ActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/ActivityEntity.cs index 7a1c5bf1..39732e04 100644 --- a/ProjectLighthouse/Types/Entities/Activity/ActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/ActivityEntity.cs @@ -11,12 +11,21 @@ public class ActivityEntity [Key] public int ActivityId { get; set; } + /// + /// The time that this event took place. + /// public DateTime Timestamp { get; set; } + /// + /// The of the that triggered this event. + /// public int UserId { get; set; } [ForeignKey(nameof(UserId))] public UserEntity User { get; set; } + /// + /// The type of this event. + /// public EventType Type { get; set; } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Entities/Activity/CommentActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/CommentActivityEntity.cs index 0c20175e..61a80e88 100644 --- a/ProjectLighthouse/Types/Entities/Activity/CommentActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/CommentActivityEntity.cs @@ -1,15 +1,38 @@ using System.ComponentModel.DataAnnotations.Schema; +using LBPUnion.ProjectLighthouse.Types.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; /// -/// Supported event types: CommentOnUser, CommentOnLevel, DeleteLevelComment +/// Supported event types: , , and . /// public class CommentActivityEntity : ActivityEntity { + /// + /// The of the that this event refers to. + /// public int CommentId { get; set; } [ForeignKey(nameof(CommentId))] public CommentEntity Comment { get; set; } +} + +public class LevelCommentActivityEntity : CommentActivityEntity +{ + [Column("SlotId")] + public int SlotId { get; set; } + + [ForeignKey(nameof(SlotId))] + public SlotEntity Slot { get; set; } +} + +public class UserCommentActivityEntity : CommentActivityEntity +{ + [Column("TargetUserId")] + 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/Activity/LevelActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/LevelActivityEntity.cs index 699a93f5..27d7ca9a 100644 --- a/ProjectLighthouse/Types/Entities/Activity/LevelActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/LevelActivityEntity.cs @@ -1,13 +1,18 @@ using System.ComponentModel.DataAnnotations.Schema; +using LBPUnion.ProjectLighthouse.Types.Activity; using LBPUnion.ProjectLighthouse.Types.Entities.Level; namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; /// -/// Supported event types: play_level, heart_level, publish_level, unheart_level, dpad_rate_level, rate_level, tag_level, mm_pick_level +/// Supported event types: , , , +/// , and . /// public class LevelActivityEntity : ActivityEntity { + /// + /// The of the that this event refers to. + /// [Column("SlotId")] public int SlotId { get; set; } diff --git a/ProjectLighthouse/Types/Entities/Activity/NewsActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/NewsActivityEntity.cs index 32a768ca..59daccaa 100644 --- a/ProjectLighthouse/Types/Entities/Activity/NewsActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/NewsActivityEntity.cs @@ -1,13 +1,22 @@ using System.ComponentModel.DataAnnotations.Schema; +using LBPUnion.ProjectLighthouse.Types.Activity; using LBPUnion.ProjectLighthouse.Types.Entities.Website; namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; /// -/// Supported event types: NewsPost +/// Supported event types: . +/// +/// +/// This event type can only be grouped with other . +/// +/// /// public class NewsActivityEntity : ActivityEntity { + /// + /// The of the that this event refers to. + /// public int NewsId { get; set; } [ForeignKey(nameof(NewsId))] diff --git a/ProjectLighthouse/Types/Entities/Activity/PhotoActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/PhotoActivityEntity.cs index 6db40d59..bf1d9083 100644 --- a/ProjectLighthouse/Types/Entities/Activity/PhotoActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/PhotoActivityEntity.cs @@ -1,16 +1,38 @@ using System.ComponentModel.DataAnnotations.Schema; +using LBPUnion.ProjectLighthouse.Types.Activity; +using LBPUnion.ProjectLighthouse.Types.Entities.Level; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; /// -/// Supported event types: UploadPhoto +/// Supported event types: . /// public class PhotoActivityEntity : ActivityEntity { + /// + /// The of the that this event refers to. + /// public int PhotoId { get; set; } [ForeignKey(nameof(PhotoId))] public PhotoEntity Photo { get; set; } - +} + +public class LevelPhotoActivity : PhotoActivityEntity +{ + [Column("SlotId")] + public int SlotId { get; set; } + + [ForeignKey(nameof(SlotId))] + public SlotEntity Slot { get; set; } +} + +public class UserPhotoActivity : PhotoActivityEntity +{ + [Column("TargetUserId")] + 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/Activity/PlaylistActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/PlaylistActivityEntity.cs index 4d535459..9d4ef06b 100644 --- a/ProjectLighthouse/Types/Entities/Activity/PlaylistActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/PlaylistActivityEntity.cs @@ -1,13 +1,17 @@ using System.ComponentModel.DataAnnotations.Schema; +using LBPUnion.ProjectLighthouse.Types.Activity; using LBPUnion.ProjectLighthouse.Types.Entities.Level; namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; /// -/// Supported event types: CreatePlaylist, HeartPlaylist +/// Supported event types: and . /// public class PlaylistActivityEntity : ActivityEntity { + /// + /// The of the that this event refers to. + /// [Column("PlaylistId")] public int PlaylistId { get; set; } @@ -16,15 +20,19 @@ public class PlaylistActivityEntity : ActivityEntity } /// -/// Supported event types: AddLevelToPlaylist +/// Supported event types: . +/// /// /// The relationship between and -/// is slightly hacky but it allows conditional reuse of columns from other ActivityEntity's -/// -/// +/// is slightly hacky but it allows us to reuse columns that would normally only be user with other types. +/// +/// /// public class PlaylistWithSlotActivityEntity : ActivityEntity { + /// + /// The of the that this event refers to. + /// [Column("PlaylistId")] public int PlaylistId { get; set; } @@ -33,7 +41,11 @@ public class PlaylistWithSlotActivityEntity : ActivityEntity /// /// This reuses the SlotId column of but has no ForeignKey definition so that it can be null - /// It effectively serves as extra storage for PlaylistActivityEntity to use for the AddLevelToPlaylistEvent + /// + /// + /// It effectively serves as extra storage for PlaylistActivityEntity to use for the AddLevelToPlaylistEvent + /// + /// /// [Column("SlotId")] public int SlotId { get; set; } diff --git a/ProjectLighthouse/Types/Entities/Activity/ReviewActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/ReviewActivityEntity.cs index 9a722601..e824541c 100644 --- a/ProjectLighthouse/Types/Entities/Activity/ReviewActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/ReviewActivityEntity.cs @@ -1,15 +1,25 @@ using System.ComponentModel.DataAnnotations.Schema; +using LBPUnion.ProjectLighthouse.Types.Activity; using LBPUnion.ProjectLighthouse.Types.Entities.Level; namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; /// -/// Supported event types: DpadRateLevel, ReviewLevel, RateLevel, TagLevel +/// Supported event types: , , , and . /// public class ReviewActivityEntity : ActivityEntity { + /// + /// The of the that this event refers to. + /// public int ReviewId { get; set; } [ForeignKey(nameof(ReviewId))] public ReviewEntity Review { get; set; } + + [Column("SlotId")] + public int SlotId { get; set; } + + [ForeignKey(nameof(SlotId))] + public SlotEntity Slot { get; set; } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Entities/Activity/ScoreActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/ScoreActivityEntity.cs index 17c500ac..4ecc8384 100644 --- a/ProjectLighthouse/Types/Entities/Activity/ScoreActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/ScoreActivityEntity.cs @@ -1,15 +1,25 @@ using System.ComponentModel.DataAnnotations.Schema; +using LBPUnion.ProjectLighthouse.Types.Activity; using LBPUnion.ProjectLighthouse.Types.Entities.Level; namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; /// -/// Supported event types: Score +/// Supported event types: . /// public class ScoreActivityEntity : ActivityEntity { + /// + /// The of the that this event refers to. + /// public int ScoreId { get; set; } [ForeignKey(nameof(ScoreId))] public ScoreEntity Score { get; set; } + + [Column("SlotId")] + public int SlotId { get; set; } + + [ForeignKey(nameof(SlotId))] + public SlotEntity Slot { get; set; } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/Entities/Activity/UserActivityEntity.cs b/ProjectLighthouse/Types/Entities/Activity/UserActivityEntity.cs index 89d27e8b..f1756f5b 100644 --- a/ProjectLighthouse/Types/Entities/Activity/UserActivityEntity.cs +++ b/ProjectLighthouse/Types/Entities/Activity/UserActivityEntity.cs @@ -1,13 +1,18 @@ using System.ComponentModel.DataAnnotations.Schema; +using LBPUnion.ProjectLighthouse.Types.Activity; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; namespace LBPUnion.ProjectLighthouse.Types.Entities.Activity; /// -/// Supported event types: HeartUser, UnheartUser +/// Supported event types: and . /// public class UserActivityEntity : ActivityEntity { + /// + /// The of the that this event refers to. + /// + [Column("TargetUserId")] public int TargetUserId { get; set; } [ForeignKey(nameof(TargetUserId))] diff --git a/ProjectLighthouse/Types/Serialization/Activity/Events/GameEvent.cs b/ProjectLighthouse/Types/Serialization/Activity/Events/GameEvent.cs index 649907d5..21d28314 100644 --- a/ProjectLighthouse/Types/Serialization/Activity/Events/GameEvent.cs +++ b/ProjectLighthouse/Types/Serialization/Activity/Events/GameEvent.cs @@ -58,7 +58,7 @@ public class GameEvent : ILbpSerializable, INeedsPreparationForSerialization public static IEnumerable CreateFromActivities(IEnumerable activities) { - List events = new(); + List events = []; List> typeGroups = activities.GroupBy(g => g.Activity.Type).ToList(); foreach (IGrouping typeGroup in typeGroups) { diff --git a/ProjectLighthouse/Types/Serialization/Activity/GameStream.cs b/ProjectLighthouse/Types/Serialization/Activity/GameStream.cs index 3f41b025..f2d2d7a2 100644 --- a/ProjectLighthouse/Types/Serialization/Activity/GameStream.cs +++ b/ProjectLighthouse/Types/Serialization/Activity/GameStream.cs @@ -26,21 +26,34 @@ namespace LBPUnion.ProjectLighthouse.Types.Serialization.Activity; [XmlRoot("stream")] public class GameStream : ILbpSerializable, INeedsPreparationForSerialization { + /// + /// A list of that should be included in the root + /// of the stream object. These will be loaded into + /// [XmlIgnore] public List SlotIds { get; set; } + /// + /// A list of that should be included in the root + /// of the stream object. These will be loaded into + /// [XmlIgnore] public List UserIds { get; set; } + /// + /// A list of that should be included in the root + /// of the stream object. These will be loaded into + /// [XmlIgnore] public List PlaylistIds { get; set; } + /// + /// A list of that should be included in the root + /// of the stream object. These will be loaded into + /// [XmlIgnore] public List NewsIds { get; set; } - [XmlIgnore] - private int TargetUserId { get; set; } - [XmlIgnore] private GameVersion TargetGame { get; set; } @@ -77,7 +90,7 @@ public class GameStream : ILbpSerializable, INeedsPreparationForSerialization public async Task PrepareSerialization(DatabaseContext database) { - this.Slots = await LoadEntities(this.SlotIds, slot => SlotBase.CreateFromEntity(slot, this.TargetGame, this.TargetUserId), s => s.Type == SlotType.User); + this.Slots = await LoadEntities(this.SlotIds, slot => SlotBase.CreateFromEntity(slot, this.TargetGame, 0), s => s.Type == SlotType.User); this.Users = await LoadEntities(this.UserIds, user => GameUser.CreateFromEntity(user, this.TargetGame)); this.Playlists = await LoadEntities(this.PlaylistIds, GamePlaylist.CreateFromEntity); this.News = await LoadEntities(this.NewsIds, a => GameNewsObject.CreateFromEntity(a, this.TargetGame)); @@ -86,16 +99,16 @@ public class GameStream : ILbpSerializable, INeedsPreparationForSerialization async Task> LoadEntities(List ids, Func transformation, Func predicate = null) where TFrom : class { - List results = new(); + List results = []; if (ids.Count <= 0) return null; foreach (int id in ids) { TFrom entity = await database.Set().FindAsync(id); - if (predicate != null && !predicate(entity)) continue; - if (entity == null) continue; + if (predicate != null && !predicate(entity)) continue; + results.Add(transformation(entity)); } @@ -108,7 +121,6 @@ public class GameStream : ILbpSerializable, INeedsPreparationForSerialization { GameStream gameStream = new() { - TargetUserId = token.UserId, TargetGame = token.GameVersion, StartTimestamp = startTimestamp, EndTimestamp = endTimestamp, diff --git a/ProjectLighthouse/Types/Serialization/Activity/GameStreamGroup.cs b/ProjectLighthouse/Types/Serialization/Activity/GameStreamGroup.cs index 4afc678b..e4fea532 100644 --- a/ProjectLighthouse/Types/Serialization/Activity/GameStreamGroup.cs +++ b/ProjectLighthouse/Types/Serialization/Activity/GameStreamGroup.cs @@ -37,8 +37,6 @@ public class GameStreamGroup : ILbpSerializable [XmlArray("events")] [XmlArrayItem("event")] [DefaultValue(null)] - // ReSharper disable once MemberCanBePrivate.Global - // (the serializer can't see this if it's private) public List Events { get; set; } public static GameStreamGroup CreateFromGroup(OuterActivityGroup group) @@ -56,8 +54,7 @@ public class GameStreamGroup : ILbpSerializable g.Key.TargetId, streamGroup => { - streamGroup.Timestamp = - g.MaxBy(a => a.Activity.Timestamp).Activity.Timestamp.ToUnixTimeMilliseconds(); + streamGroup.Timestamp = g.Max(a => a.Activity.Timestamp).ToUnixTimeMilliseconds(); streamGroup.Events = GameEvent.CreateFromActivities(g).ToList(); })) .ToList());