From 3b0a476d1b70d84a4b834dcd6deccfef8b613ee6 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Wed, 29 Oct 2025 20:20:50 +0100 Subject: [PATCH 1/4] Seperate pin relations by platform --- Refresh.Database/GameDatabaseContext.Pins.cs | 68 ++++++------- ...029184346_SeperatePinProgressByPlatform.cs | 83 ++++++++++++++++ .../GameDatabaseContextModelSnapshot.cs | 12 ++- .../Models/Relations/PinProgressRelation.cs | 10 +- .../Models/Relations/ProfilePinRelation.cs | 7 +- .../Endpoints/AuthenticationApiEndpoints.cs | 2 +- .../Endpoints/LevelApiEndpoints.cs | 3 +- .../DataTypes/Response/GameUserResponse.cs | 2 +- .../Endpoints/Levels/LeaderboardEndpoints.cs | 8 +- .../Endpoints/UserEndpoints.cs | 7 +- .../Tests/Pins/PinProgressPlatformTests.cs | 97 +++++++++++++++++++ .../Tests/Pins/ScorePinTests.cs | 12 +-- 12 files changed, 250 insertions(+), 61 deletions(-) create mode 100644 Refresh.Database/Migrations/20251029184346_SeperatePinProgressByPlatform.cs create mode 100644 RefreshTests.GameServer/Tests/Pins/PinProgressPlatformTests.cs diff --git a/Refresh.Database/GameDatabaseContext.Pins.cs b/Refresh.Database/GameDatabaseContext.Pins.cs index 193eff71..443a13a7 100644 --- a/Refresh.Database/GameDatabaseContext.Pins.cs +++ b/Refresh.Database/GameDatabaseContext.Pins.cs @@ -7,11 +7,10 @@ namespace Refresh.Database; public partial class GameDatabaseContext // Pins { - public void UpdateUserPinProgress(Dictionary pinProgressUpdates, GameUser user, TokenGame game) + public void UpdateUserPinProgress(Dictionary pinProgressUpdates, GameUser user, bool isBeta, TokenPlatform platform) { DateTimeOffset now = this._time.Now; - bool isBeta = game == TokenGame.BetaBuild; - IEnumerable existingProgresses = this.GetPinProgressesByUser(user, isBeta); + IEnumerable existingProgresses = this.GetPinProgressesByUser(user, isBeta, platform); List descendingProgressPins = [ (long)ServerPins.TopXOfAnyStoryLevelWithOver50Scores, @@ -34,6 +33,7 @@ public void UpdateUserPinProgress(Dictionary pinProgressUpdates, Game FirstPublished = now, LastUpdated = now, IsBeta = isBeta, + Platform = platform, }; this.PinProgressRelations.Add(newRelation); continue; @@ -53,10 +53,12 @@ public void UpdateUserPinProgress(Dictionary pinProgressUpdates, Game this.SaveChanges(); } - public void UpdateUserProfilePins(List pinUpdates, GameUser user, TokenGame game) + public void UpdateUserProfilePins(List pinUpdates, GameUser user, TokenGame game, TokenPlatform platform) { - IEnumerable existingProgressIds = this.GetPinProgressesByUser(user, game == TokenGame.BetaBuild).Select(p => p.PinId); - IEnumerable existingProfilePins = this.GetProfilePinsByUser(user, game); + IEnumerable existingProgressIds = this + .GetPinProgressesByUser(user, game == TokenGame.BetaBuild, platform) + .Select(p => p.PinId); + IEnumerable existingProfilePins = this.GetProfilePinsByUser(user, game, platform); DateTimeOffset now = this._time.Now; for (int i = 0; i < pinUpdates.Count; i++) @@ -80,6 +82,7 @@ public void UpdateUserProfilePins(List pinUpdates, GameUser user, TokenGam PublisherId = user.UserId, Index = i, Game = game, + Platform = platform, Timestamp = now, }); } @@ -93,10 +96,10 @@ public void UpdateUserProfilePins(List pinUpdates, GameUser user, TokenGam this.SaveChanges(); } - public PinProgressRelation UpdateUserPinProgressToLowest(long pinId, int newProgressValue, GameUser user, bool isBeta) + public PinProgressRelation UpdateUserPinProgressToLowest(long pinId, int newProgressValue, GameUser user, bool isBeta, TokenPlatform platform) { // Get pin progress if it exists already - PinProgressRelation? progressToUpdate = this.GetUserPinProgress(pinId, user, isBeta); + PinProgressRelation? progressToUpdate = this.GetUserPinProgress(pinId, user, isBeta, platform); DateTimeOffset now = this._time.Now; if (progressToUpdate == null) @@ -126,31 +129,15 @@ public PinProgressRelation UpdateUserPinProgressToLowest(long pinId, int newProg return progressToUpdate!; } - public void IncrementUserPinProgress(long pinId, int progressToAdd, GameUser user) - { - this.IncrementUserPinProgressInternal(pinId, progressToAdd, user, true); - this.IncrementUserPinProgressInternal(pinId, progressToAdd, user, false); - - this.SaveChanges(); - } - - public PinProgressRelation IncrementUserPinProgress(long pinId, int progressToAdd, GameUser user, bool isBeta) - { - PinProgressRelation relation = this.IncrementUserPinProgressInternal(pinId, progressToAdd, user, isBeta); - this.SaveChanges(); - - return relation; - } - - private PinProgressRelation IncrementUserPinProgressInternal(long pinId, int progressToAdd, GameUser user, bool isBeta) + public PinProgressRelation IncrementUserPinProgress(long pinId, int progressToAdd, GameUser user, bool isBeta, TokenPlatform platform) { // Get pin progress if it exists already - PinProgressRelation? progressToUpdate = this.GetUserPinProgress(pinId, user, isBeta); + PinProgressRelation? progressToUpdate = this.GetUserPinProgress(pinId, user, isBeta, platform); DateTimeOffset now = this._time.Now; if (progressToUpdate == null) { - PinProgressRelation newRelation = new() + progressToUpdate = new() { PinId = pinId, Progress = progressToAdd, @@ -159,10 +146,9 @@ private PinProgressRelation IncrementUserPinProgressInternal(long pinId, int pro FirstPublished = now, LastUpdated = now, IsBeta = isBeta, + Platform = platform, }; - - this.PinProgressRelations.Add(newRelation); - return newRelation; + this.PinProgressRelations.Add(progressToUpdate); } else { @@ -170,25 +156,27 @@ private PinProgressRelation IncrementUserPinProgressInternal(long pinId, int pro progressToUpdate.LastUpdated = now; } + this.SaveChanges(); return progressToUpdate; } - private IEnumerable GetPinProgressesByUser(GameUser user, bool isBeta) + private IEnumerable GetPinProgressesByUser(GameUser user, bool isBeta, TokenPlatform platform) => this.PinProgressRelations - .Where(p => p.PublisherId == user.UserId && p.IsBeta == isBeta) + .Where(p => p.PublisherId == user.UserId && (p.IsBeta == isBeta && p.Platform == platform || p.Platform == TokenPlatform.Website)) .OrderByDescending(p => p.LastUpdated); - public DatabaseList GetPinProgressesByUser(GameUser user, TokenGame game, int skip, int count) - => new(this.GetPinProgressesByUser(user, game == TokenGame.BetaBuild), skip, count); + public DatabaseList GetPinProgressesByUser(GameUser user, bool isBeta, TokenPlatform platform, int skip, int count) + => new(this.GetPinProgressesByUser(user, isBeta, platform), skip, count); - public PinProgressRelation? GetUserPinProgress(long pinId, GameUser user, bool isBeta) - => this.PinProgressRelations.FirstOrDefault(p => p.PinId == pinId && p.PublisherId == user.UserId && p.IsBeta == isBeta); + public PinProgressRelation? GetUserPinProgress(long pinId, GameUser user, bool isBeta, TokenPlatform platform) + => this.PinProgressRelations.FirstOrDefault(p => p.PinId == pinId && p.PublisherId == user.UserId + && (p.IsBeta == isBeta && p.Platform == platform || p.Platform == TokenPlatform.Website)); - private IEnumerable GetProfilePinsByUser(GameUser user, TokenGame game) + private IEnumerable GetProfilePinsByUser(GameUser user, TokenGame game, TokenPlatform platform) => this.ProfilePinRelations - .Where(p => p.PublisherId == user.UserId && p.Game == game) + .Where(p => p.PublisherId == user.UserId && p.Game == game && p.Platform == platform) .OrderBy(p => p.Index); - public DatabaseList GetProfilePinsByUser(GameUser user, TokenGame game, int skip, int count) - => new(this.GetProfilePinsByUser(user, game), skip, count); + public DatabaseList GetProfilePinsByUser(GameUser user, TokenGame game, TokenPlatform platform, int skip, int count) + => new(this.GetProfilePinsByUser(user, game, platform), skip, count); } \ No newline at end of file diff --git a/Refresh.Database/Migrations/20251029184346_SeperatePinProgressByPlatform.cs b/Refresh.Database/Migrations/20251029184346_SeperatePinProgressByPlatform.cs new file mode 100644 index 00000000..4598b2d6 --- /dev/null +++ b/Refresh.Database/Migrations/20251029184346_SeperatePinProgressByPlatform.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Refresh.Database.Models.Authentication; +using Refresh.Database.Models.Pins; + +#nullable disable + +namespace Refresh.Database.Migrations +{ + /// + [DbContext(typeof(GameDatabaseContext))] + [Migration("20251029184346_SeperatePinProgressByPlatform")] + public partial class SeperatePinProgressByPlatform : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // RPCS3 is the safest default value here, as game achievements on RPCS3 are considered the least valuable. + // Also, the game will sync progress and profile pins after login anyway, so practically no information is lost here. + migrationBuilder.AddColumn( + name: "Platform", + table: "ProfilePinRelations", + type: "integer", + nullable: false, + defaultValue: TokenPlatform.RPCS3); + + migrationBuilder.AddColumn( + name: "Platform", + table: "PinProgressRelations", + type: "integer", + nullable: false, + defaultValue: TokenPlatform.RPCS3); + + migrationBuilder.DropPrimaryKey( + name: "PK_ProfilePinRelations", + table: "ProfilePinRelations"); + + migrationBuilder.DropPrimaryKey( + name: "PK_PinProgressRelations", + table: "PinProgressRelations"); + + migrationBuilder.AddPrimaryKey( + name: "PK_ProfilePinRelations", + table: "ProfilePinRelations", + columns: ["Index", "PublisherId", "Game", "Platform"]); + + migrationBuilder.AddPrimaryKey( + name: "PK_PinProgressRelations", + table: "PinProgressRelations", + columns: ["PinId", "PublisherId", "IsBeta", "Platform"]); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Platform", + table: "ProfilePinRelations"); + + migrationBuilder.DropColumn( + name: "Platform", + table: "PinProgressRelations"); + + migrationBuilder.DropPrimaryKey( + name: "PK_ProfilePinRelations", + table: "ProfilePinRelations"); + + migrationBuilder.DropPrimaryKey( + name: "PK_PinProgressRelations", + table: "PinProgressRelations"); + + migrationBuilder.AddPrimaryKey( + name: "PK_ProfilePinRelations", + table: "ProfilePinRelations", + columns: ["Index", "PublisherId", "Game"]); + + migrationBuilder.AddPrimaryKey( + name: "PK_PinProgressRelations", + table: "PinProgressRelations", + columns: ["PinId", "PublisherId", "IsBeta"]); + } + } +} diff --git a/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs b/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs index 2ee620b8..62b21258 100644 --- a/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs +++ b/Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs @@ -18,7 +18,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "9.0.8") + .HasAnnotation("ProductVersion", "9.0.9") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -934,6 +934,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsBeta") .HasColumnType("boolean"); + b.Property("Platform") + .HasColumnType("integer"); + b.Property("FirstPublished") .HasColumnType("timestamp with time zone"); @@ -943,7 +946,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Progress") .HasColumnType("integer"); - b.HasKey("PinId", "PublisherId", "IsBeta"); + b.HasKey("PinId", "PublisherId", "IsBeta", "Platform"); b.HasIndex("PublisherId"); @@ -1015,13 +1018,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Game") .HasColumnType("integer"); + b.Property("Platform") + .HasColumnType("integer"); + b.Property("PinId") .HasColumnType("bigint"); b.Property("Timestamp") .HasColumnType("timestamp with time zone"); - b.HasKey("Index", "PublisherId", "Game"); + b.HasKey("Index", "PublisherId", "Game", "Platform"); b.HasIndex("PublisherId"); diff --git a/Refresh.Database/Models/Relations/PinProgressRelation.cs b/Refresh.Database/Models/Relations/PinProgressRelation.cs index 20d59ec0..7154184c 100644 --- a/Refresh.Database/Models/Relations/PinProgressRelation.cs +++ b/Refresh.Database/Models/Relations/PinProgressRelation.cs @@ -1,11 +1,12 @@ using MongoDB.Bson; +using Refresh.Database.Models.Authentication; using Refresh.Database.Models.Users; namespace Refresh.Database.Models.Relations; #nullable disable -[PrimaryKey(nameof(PinId), nameof(PublisherId), nameof(IsBeta))] +[PrimaryKey(nameof(PinId), nameof(PublisherId), nameof(IsBeta), nameof(Platform))] public partial class PinProgressRelation { /// @@ -26,4 +27,11 @@ public partial class PinProgressRelation /// for these game groups seperately. /// public bool IsBeta { get; set; } + + /// + /// Seperates pin progresses per platform, to not let progress transfer inbetween PS3, RPCS3 and especially Vita, + /// as that's commonly unwanted behaviour. Maybe figure out how to make this be opt-out in the future? + /// Website = this pin is universal across platforms and games (e.g. LBP.me pin) + /// + public TokenPlatform Platform { get; set; } } \ No newline at end of file diff --git a/Refresh.Database/Models/Relations/ProfilePinRelation.cs b/Refresh.Database/Models/Relations/ProfilePinRelation.cs index de54015b..9608b190 100644 --- a/Refresh.Database/Models/Relations/ProfilePinRelation.cs +++ b/Refresh.Database/Models/Relations/ProfilePinRelation.cs @@ -6,7 +6,7 @@ namespace Refresh.Database.Models.Relations; #nullable disable -[PrimaryKey(nameof(Index), nameof(PublisherId), nameof(Game))] +[PrimaryKey(nameof(Index), nameof(PublisherId), nameof(Game), nameof(Platform))] public partial class ProfilePinRelation { public long PinId { get; set; } @@ -26,5 +26,10 @@ public partial class ProfilePinRelation /// public TokenGame Game { get; set; } + /// + /// Since pin progresses are split per platforms now, we also have to split profile pins aswell + /// + public TokenPlatform Platform { get; set; } + public DateTimeOffset Timestamp { get; set; } } \ No newline at end of file diff --git a/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs b/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs index 85f67e25..e90122c0 100644 --- a/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs +++ b/Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs @@ -109,7 +109,7 @@ public ApiResponse Authenticate(RequestContext conte context.Logger.LogInfo(BunkumCategory.Authentication, $"{user} successfully logged in through the API"); // Update pin progress for signing into the API - database.IncrementUserPinProgress((long)ServerPins.SignIntoWebsite, 1, user); + database.IncrementUserPinProgress((long)ServerPins.SignIntoWebsite, 1, user, false, TokenPlatform.Website); return new ApiAuthenticationResponse { diff --git a/Refresh.Interfaces.APIv3/Endpoints/LevelApiEndpoints.cs b/Refresh.Interfaces.APIv3/Endpoints/LevelApiEndpoints.cs index 7221ca41..8b156c3e 100644 --- a/Refresh.Interfaces.APIv3/Endpoints/LevelApiEndpoints.cs +++ b/Refresh.Interfaces.APIv3/Endpoints/LevelApiEndpoints.cs @@ -8,6 +8,7 @@ using Refresh.Core.Services; using Refresh.Core.Types.Data; using Refresh.Database; +using Refresh.Database.Models.Authentication; using Refresh.Database.Models.Levels; using Refresh.Database.Models.Pins; using Refresh.Database.Models.Users; @@ -175,7 +176,7 @@ public ApiOkResponse QueueLevel(RequestContext context, GameDatabaseContext data database.QueueLevel(level, user); // Update pin progress for queueing a level through the API - database.IncrementUserPinProgress((long)ServerPins.QueueLevelOnWebsite, 1, user); + database.IncrementUserPinProgress((long)ServerPins.QueueLevelOnWebsite, 1, user, false, TokenPlatform.Website); return new ApiOkResponse(); } diff --git a/Refresh.Interfaces.Game/Endpoints/DataTypes/Response/GameUserResponse.cs b/Refresh.Interfaces.Game/Endpoints/DataTypes/Response/GameUserResponse.cs index f5b60095..ab91b27a 100644 --- a/Refresh.Interfaces.Game/Endpoints/DataTypes/Response/GameUserResponse.cs +++ b/Refresh.Interfaces.Game/Endpoints/DataTypes/Response/GameUserResponse.cs @@ -115,7 +115,7 @@ public class GameUserResponse : IDataConvertableFrom } if (game is not TokenGame.LittleBigPlanet1 or TokenGame.LittleBigPlanetPSP) { - response.ProfilePins = dataContext.Database.GetProfilePinsByUser(old, dataContext.Game, 0, 3) + response.ProfilePins = dataContext.Database.GetProfilePinsByUser(old, dataContext.Game, dataContext.Platform, 0, 3) .Items.Select(p => p.PinId).ToList(); } diff --git a/Refresh.Interfaces.Game/Endpoints/Levels/LeaderboardEndpoints.cs b/Refresh.Interfaces.Game/Endpoints/Levels/LeaderboardEndpoints.cs index b3483354..d18597cc 100644 --- a/Refresh.Interfaces.Game/Endpoints/Levels/LeaderboardEndpoints.cs +++ b/Refresh.Interfaces.Game/Endpoints/Levels/LeaderboardEndpoints.cs @@ -174,12 +174,12 @@ private void AwardScoreboardPins(DatabaseList scores, DataContext if (isStoryLevel) { dataContext.Database.UpdateUserPinProgressToLowest((long)ServerPins.TopXOfAnyStoryLevelWithOver50Scores, - rankingInPercent, user, isGameBetaBuild); + rankingInPercent, user, isGameBetaBuild, dataContext.Platform); } else { dataContext.Database.UpdateUserPinProgressToLowest((long)ServerPins.TopXOfAnyCommunityLevelWithOver50Scores, - rankingInPercent, user, isGameBetaBuild); + rankingInPercent, user, isGameBetaBuild, dataContext.Platform); } // Update on how many story/user levels the user's ranking is in the top 25% (top 1/4th) of the leaderboard or below @@ -188,12 +188,12 @@ private void AwardScoreboardPins(DatabaseList scores, DataContext if (isStoryLevel) { dataContext.Database.IncrementUserPinProgress((long)ServerPins.TopFourthOfXStoryLevelsWithOver50Scores, - 1, user, isGameBetaBuild); + 1, user, isGameBetaBuild, dataContext.Platform); } else { dataContext.Database.IncrementUserPinProgress((long)ServerPins.TopFourthOfXCommunityLevelsWithOver50Scores, - 1, user, isGameBetaBuild); + 1, user, isGameBetaBuild, dataContext.Platform); } } diff --git a/Refresh.Interfaces.Game/Endpoints/UserEndpoints.cs b/Refresh.Interfaces.Game/Endpoints/UserEndpoints.cs index 8df14594..d0e824a3 100644 --- a/Refresh.Interfaces.Game/Endpoints/UserEndpoints.cs +++ b/Refresh.Interfaces.Game/Endpoints/UserEndpoints.cs @@ -10,6 +10,7 @@ using Refresh.Core.Types.Data; using Refresh.Database; using Refresh.Database.Models.Authentication; +using Refresh.Database.Models.Pins; using Refresh.Database.Models.Users; using Refresh.Interfaces.Game.Endpoints.DataTypes.Response; using Refresh.Interfaces.Game.Types.Lists; @@ -176,7 +177,7 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont if (pinProgresses.Count > 0) { - dataContext.Database.UpdateUserPinProgress(pinProgresses, user, dataContext.Game); + dataContext.Database.UpdateUserPinProgress(pinProgresses, user, dataContext.Game == TokenGame.Website, dataContext.Platform); } // Users can only have 3 pins set on their profile @@ -185,7 +186,7 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont if (body.ProfilePins.Count > 0) { - dataContext.Database.UpdateUserProfilePins(body.ProfilePins, user, dataContext.Game); + dataContext.Database.UpdateUserProfilePins(body.ProfilePins, user, dataContext.Game, dataContext.Platform); } // Return newly updated pins (LBP2 and 3 update their pin progresses if there are higher progress values @@ -198,6 +199,6 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont public SerializedPins GetPins(RequestContext context, DataContext dataContext, GameUser user) => SerializedPins.FromOld ( - dataContext.Database.GetPinProgressesByUser(user, dataContext.Game, 0, 999).Items + dataContext.Database.GetPinProgressesByUser(user, dataContext.Game == TokenGame.BetaBuild, dataContext.Platform, 0, 999).Items ); } \ No newline at end of file diff --git a/RefreshTests.GameServer/Tests/Pins/PinProgressPlatformTests.cs b/RefreshTests.GameServer/Tests/Pins/PinProgressPlatformTests.cs new file mode 100644 index 00000000..e2bb3420 --- /dev/null +++ b/RefreshTests.GameServer/Tests/Pins/PinProgressPlatformTests.cs @@ -0,0 +1,97 @@ +using Refresh.Database; +using Refresh.Database.Models.Authentication; +using Refresh.Database.Models.Pins; +using Refresh.Database.Models.Relations; +using Refresh.Database.Models.Users; + +namespace RefreshTests.GameServer.Tests.Pins; + +public class PinProgressPlatformTests : GameServerTest +{ + [Test] + public void PlatformedPinsSeperatedByPlatformTest() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + + // ROUND 1 - Create a pin which has a non-website platform (not universal) + context.Database.IncrementUserPinProgress((long)ServerPins.TopXOfAnyStoryLevelWithOver50Scores, 420, user, false, TokenPlatform.PS3); + + // Pin should appear when searching for this specific game type and platform + DatabaseList relations = context.Database.GetPinProgressesByUser(user, false, TokenPlatform.PS3, 0, 300); + Assert.That(relations.Items.Count(), Is.EqualTo(1)); + Assert.That(relations.Items.First().PinId, Is.EqualTo((long)ServerPins.TopXOfAnyStoryLevelWithOver50Scores)); + + // Pin should not appear if searched game type is wrong + relations = context.Database.GetPinProgressesByUser(user, true, TokenPlatform.PS3, 0, 300); + Assert.That(relations.Items.Count(), Is.Zero); + + // Pin should not appear if searched platform is wrong + relations = context.Database.GetPinProgressesByUser(user, false, TokenPlatform.Vita, 0, 300); + Assert.That(relations.Items.Count(), Is.Zero); + + // ROUND 2 - Update the pin to lowest + context.Database.UpdateUserPinProgressToLowest((long)ServerPins.TopXOfAnyStoryLevelWithOver50Scores, 210, user, false, TokenPlatform.PS3); + + // Pin should appear when searching for this specific game type and platform + relations = context.Database.GetPinProgressesByUser(user, false, TokenPlatform.PS3, 0, 300); + Assert.That(relations.Items.Count(), Is.EqualTo(1)); + Assert.That(relations.Items.First().PinId, Is.EqualTo((long)ServerPins.TopXOfAnyStoryLevelWithOver50Scores)); + + // Pin should not appear if searched game type is wrong + relations = context.Database.GetPinProgressesByUser(user, true, TokenPlatform.PS3, 0, 300); + Assert.That(relations.Items.Count(), Is.Zero); + + // Pin should not appear if searched platform is wrong + relations = context.Database.GetPinProgressesByUser(user, false, TokenPlatform.Vita, 0, 300); + Assert.That(relations.Items.Count(), Is.Zero); + } + + [Test] + public void UniversalPinsAppearOnAllPlatformsTest() + { + using TestContext context = this.GetServer(); + GameUser user = context.CreateUser(); + + // ROUND 1 - Create a pin which is "universal" (platform is Website) + context.Database.IncrementUserPinProgress((long)ServerPins.SignIntoWebsite, 420, user, false, TokenPlatform.Website); + + // Pin should appear when searching for any specific game type and platform + DatabaseList relations = context.Database.GetPinProgressesByUser(user, false, TokenPlatform.PS3, 0, 300); + Assert.That(relations.Items.Count(), Is.EqualTo(1)); + Assert.That(relations.Items.First().PinId, Is.EqualTo((long)ServerPins.SignIntoWebsite)); + + // Pin should appear if searched game type is wrong + relations = context.Database.GetPinProgressesByUser(user, true, TokenPlatform.PS3, 0, 300); + Assert.That(relations.Items.Count(), Is.EqualTo(1)); + Assert.That(relations.Items.First().PinId, Is.EqualTo((long)ServerPins.SignIntoWebsite)); + + // Pin should appear if searched platform is wrong + relations = context.Database.GetPinProgressesByUser(user, false, TokenPlatform.Vita, 0, 300); + Assert.That(relations.Items.Count(), Is.EqualTo(1)); + Assert.That(relations.Items.First().PinId, Is.EqualTo((long)ServerPins.SignIntoWebsite)); + + // Pin should appear if both are wrong + relations = context.Database.GetPinProgressesByUser(user, true, TokenPlatform.RPCS3, 0, 300); + Assert.That(relations.Items.Count(), Is.EqualTo(1)); + Assert.That(relations.Items.First().PinId, Is.EqualTo((long)ServerPins.SignIntoWebsite)); + + // ROUND 2 - Update the pin to lowest (through the game) + context.Database.UpdateUserPinProgressToLowest((long)ServerPins.SignIntoWebsite, 210, user, false, TokenPlatform.PS3); + + // Pin should appear if searched game type is wrong + relations = context.Database.GetPinProgressesByUser(user, true, TokenPlatform.PS3, 0, 300); + Assert.That(relations.Items.Count(), Is.EqualTo(1)); + Assert.That(relations.Items.First().PinId, Is.EqualTo((long)ServerPins.SignIntoWebsite)); + + // Pin should appear if searched platform is wrong + relations = context.Database.GetPinProgressesByUser(user, false, TokenPlatform.Vita, 0, 300); + Assert.That(relations.Items.Count(), Is.EqualTo(1)); + Assert.That(relations.Items.First().PinId, Is.EqualTo((long)ServerPins.SignIntoWebsite)); + + // Pin should appear if both are wrong + relations = context.Database.GetPinProgressesByUser(user, true, TokenPlatform.RPCS3, 0, 300); + Assert.That(relations.Items.Count(), Is.EqualTo(1)); + Assert.That(relations.Items.First().PinId, Is.EqualTo((long)ServerPins.SignIntoWebsite)); + } +} \ No newline at end of file diff --git a/RefreshTests.GameServer/Tests/Pins/ScorePinTests.cs b/RefreshTests.GameServer/Tests/Pins/ScorePinTests.cs index 0f8475ed..23e93725 100644 --- a/RefreshTests.GameServer/Tests/Pins/ScorePinTests.cs +++ b/RefreshTests.GameServer/Tests/Pins/ScorePinTests.cs @@ -43,7 +43,7 @@ public void AchieveTopXOfLeaderboardsPin(byte scoreType, bool isStoryLevel) Assert.That(message.StatusCode, Is.EqualTo(OK)); // Ensure we now have the pin - PinProgressRelation? relation = context.Database.GetUserPinProgress(pinIdToCheck, user, false); + PinProgressRelation? relation = context.Database.GetUserPinProgress(pinIdToCheck, user, false, TokenPlatform.PS3); Assert.That(relation, Is.Not.Null); int progress = relation!.Progress; @@ -61,7 +61,7 @@ public void AchieveTopXOfLeaderboardsPin(byte scoreType, bool isStoryLevel) Assert.That(message.StatusCode, Is.EqualTo(OK)); // Ensure the pin now has a better progress value (is smaller) - relation = context.Database.GetUserPinProgress(pinIdToCheck, user, false); + relation = context.Database.GetUserPinProgress(pinIdToCheck, user, false, TokenPlatform.PS3); Assert.That(relation, Is.Not.Null); Assert.That(relation!.Progress, Is.LessThan(progress)); @@ -103,7 +103,7 @@ public void AchieveTopFourthOfXLeaderboardsPin(byte scoreType, bool isStoryLevel Assert.That(message.StatusCode, Is.EqualTo(OK)); // Ensure we now have the pin - PinProgressRelation? relation = context.Database.GetUserPinProgress(pinIdToCheck, user, false); + PinProgressRelation? relation = context.Database.GetUserPinProgress(pinIdToCheck, user, false, TokenPlatform.PS3); Assert.That(relation, Is.Not.Null); Assert.That(relation!.Progress, Is.EqualTo(1)); @@ -129,7 +129,7 @@ public void AchieveTopFourthOfXLeaderboardsPin(byte scoreType, bool isStoryLevel context.Database.Refresh(); // Ensure the pin progress has been incremented - PinProgressRelation? relation2 = context.Database.GetUserPinProgress(pinIdToCheck, user, false); + PinProgressRelation? relation2 = context.Database.GetUserPinProgress(pinIdToCheck, user, false, TokenPlatform.PS3); Assert.That(relation2, Is.Not.Null); Assert.That(relation2!.Progress, Is.EqualTo(2)); } @@ -167,7 +167,7 @@ public void RejectTopXOfLeaderboardsPinIfTooFewScores(byte scoreType, bool isSto Assert.That(message.StatusCode, Is.EqualTo(OK)); // Ensure we don't have the pin - PinProgressRelation? relation = context.Database.GetUserPinProgress(pinIdToCheck, user, false); + PinProgressRelation? relation = context.Database.GetUserPinProgress(pinIdToCheck, user, false, TokenPlatform.PS3); Assert.That(relation, Is.Null); } @@ -204,7 +204,7 @@ public void RejectTopFourthOfXLeaderboardsPinIfSkillIssue(byte scoreType, bool i Assert.That(message.StatusCode, Is.EqualTo(OK)); // Ensure we don't have the pin - PinProgressRelation? relation = context.Database.GetUserPinProgress(pinIdToCheck, user, false); + PinProgressRelation? relation = context.Database.GetUserPinProgress(pinIdToCheck, user, false, TokenPlatform.PS3); Assert.That(relation, Is.Null); } } \ No newline at end of file From 302283a4430bd1e52d303df5a99ddc9fafc03ce5 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Wed, 29 Oct 2025 21:25:55 +0100 Subject: [PATCH 2/4] Add migration to force website pin progresses to be "universal" across games --- Refresh.Database/GameDatabaseContext.Pins.cs | 12 ++++ .../CorrectWebsitePinProgressPlatform.cs | 58 +++++++++++++++++++ .../RefreshWorkerManager.cs | 1 + 3 files changed, 71 insertions(+) create mode 100644 Refresh.Interfaces.Workers/Migrations/CorrectWebsitePinProgressPlatform.cs diff --git a/Refresh.Database/GameDatabaseContext.Pins.cs b/Refresh.Database/GameDatabaseContext.Pins.cs index 443a13a7..822bd078 100644 --- a/Refresh.Database/GameDatabaseContext.Pins.cs +++ b/Refresh.Database/GameDatabaseContext.Pins.cs @@ -179,4 +179,16 @@ private IEnumerable GetProfilePinsByUser(GameUser user, Toke public DatabaseList GetProfilePinsByUser(GameUser user, TokenGame game, TokenPlatform platform, int skip, int count) => new(this.GetProfilePinsByUser(user, game, platform), skip, count); + + public void AddPinProgress(PinProgressRelation relation, bool save) + { + this.PinProgressRelations.Add(relation); + if (save) this.SaveChanges(); + } + + public void RemovePinProgresses(IEnumerable relations, bool save) + { + this.PinProgressRelations.RemoveRange(relations); + if (save) this.SaveChanges(); + } } \ No newline at end of file diff --git a/Refresh.Interfaces.Workers/Migrations/CorrectWebsitePinProgressPlatform.cs b/Refresh.Interfaces.Workers/Migrations/CorrectWebsitePinProgressPlatform.cs new file mode 100644 index 00000000..1b5e41f6 --- /dev/null +++ b/Refresh.Interfaces.Workers/Migrations/CorrectWebsitePinProgressPlatform.cs @@ -0,0 +1,58 @@ +using MongoDB.Bson; +using Refresh.Common.Time; +using Refresh.Database.Models.Authentication; +using Refresh.Database.Models.Pins; +using Refresh.Database.Models.Relations; +using Refresh.Workers; + +namespace Refresh.Interfaces.Workers.Migrations; + +public class CorrectWebsitePinProgressPlatform : MigrationJob +{ + private List websitePinIds = + [ + (long)ServerPins.HeartPlayerOnWebsite, + (long)ServerPins.QueueLevelOnWebsite, + (long)ServerPins.SignIntoWebsite, + ]; + + protected override IQueryable SortAndFilter(IQueryable query) + { + return query + .Where(p => this.websitePinIds.Contains(p.PinId)) + .OrderBy(p => p.PinId); + } + + protected override void Migrate(WorkContext context, PinProgressRelation[] batch) + { + foreach (long pinId in this.websitePinIds) + { + IEnumerable> pinsByUser = batch + .Where(r => r.PinId == pinId) + .GroupBy(r => r.PublisherId); + + foreach (IEnumerable group in pinsByUser) + { + if (!group.Any()) continue; + + // Find and migrate one progress + PinProgressRelation relationToMigrate = group.MaxBy(r => r.Progress)!; + context.Database.AddPinProgress(new() + { + PinId = relationToMigrate.PinId, + Progress = relationToMigrate.Progress, + PublisherId = relationToMigrate.PublisherId, + FirstPublished = relationToMigrate.FirstPublished, + LastUpdated = relationToMigrate.LastUpdated, + IsBeta = false, // doesn't matter here + Platform = TokenPlatform.Website, + }, false); + + // Remove all others + context.Database.RemovePinProgresses(group, false); + } + } + + context.Database.SaveChanges(); + } +} \ No newline at end of file diff --git a/Refresh.Interfaces.Workers/RefreshWorkerManager.cs b/Refresh.Interfaces.Workers/RefreshWorkerManager.cs index 3e435363..1efd034a 100644 --- a/Refresh.Interfaces.Workers/RefreshWorkerManager.cs +++ b/Refresh.Interfaces.Workers/RefreshWorkerManager.cs @@ -23,6 +23,7 @@ public static WorkerManager Create(Logger logger, IDataStore dataStore, GameData manager.AddJob(); manager.AddJob(); manager.AddJob(); + manager.AddJob(); return manager; } From 05897471d8776ee5af2b8e74da030b95c93cd61e Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Wed, 29 Oct 2025 21:26:33 +0100 Subject: [PATCH 3/4] Improve comment --- Refresh.Database/Models/Relations/PinProgressRelation.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Refresh.Database/Models/Relations/PinProgressRelation.cs b/Refresh.Database/Models/Relations/PinProgressRelation.cs index 7154184c..a7074a72 100644 --- a/Refresh.Database/Models/Relations/PinProgressRelation.cs +++ b/Refresh.Database/Models/Relations/PinProgressRelation.cs @@ -31,7 +31,9 @@ public partial class PinProgressRelation /// /// Seperates pin progresses per platform, to not let progress transfer inbetween PS3, RPCS3 and especially Vita, /// as that's commonly unwanted behaviour. Maybe figure out how to make this be opt-out in the future? - /// Website = this pin is universal across platforms and games (e.g. LBP.me pin) + /// + /// Website = this pin is "universal" across platforms and games (e.g. LBP.me pin). + /// In that case ignore both IsBeta and Platform, and show this progress on all games. /// public TokenPlatform Platform { get; set; } } \ No newline at end of file From 82efd0cd19f9a73c0ec19267ad6e85f1b21967d4 Mon Sep 17 00:00:00 2001 From: Toaster2 Date: Thu, 30 Oct 2025 17:18:11 +0100 Subject: [PATCH 4/4] Fix typo --- Refresh.Interfaces.Game/Endpoints/UserEndpoints.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Refresh.Interfaces.Game/Endpoints/UserEndpoints.cs b/Refresh.Interfaces.Game/Endpoints/UserEndpoints.cs index d247c395..1e9f98c3 100644 --- a/Refresh.Interfaces.Game/Endpoints/UserEndpoints.cs +++ b/Refresh.Interfaces.Game/Endpoints/UserEndpoints.cs @@ -177,7 +177,7 @@ public SerializedFriendsList GetFriends(RequestContext context, GameDatabaseCont if (pinProgresses.Count > 0) { - dataContext.Database.UpdateUserPinProgress(pinProgresses, user, dataContext.Game == TokenGame.Website, dataContext.Platform); + dataContext.Database.UpdateUserPinProgress(pinProgresses, user, dataContext.Game == TokenGame.BetaBuild, dataContext.Platform); } // Users can only have 3 pins set on their profile