Skip to content

Commit 3cfc17d

Browse files
authored
Split pin progress by platform (#982)
Closes #951. Progress for pins which should not be split across game and platform (e.g. LBP.me pins) can now be stored as one relation (instead of two with differing `IsGame` attribute) by setting their platform to `Website`.
2 parents 9a3ba99 + 53912d4 commit 3cfc17d

14 files changed

Lines changed: 331 additions & 61 deletions

File tree

Refresh.Database/GameDatabaseContext.Pins.cs

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
using Refresh.Database.Models.Users;
33
using Refresh.Database.Models.Relations;
44
using Refresh.Database.Models.Pins;
5+
using MongoDB.Bson;
56

67
namespace Refresh.Database;
78

89
public partial class GameDatabaseContext // Pins
910
{
10-
public void UpdateUserPinProgress(Dictionary<long, int> pinProgressUpdates, GameUser user, TokenGame game)
11+
public void UpdateUserPinProgress(Dictionary<long, int> pinProgressUpdates, GameUser user, bool isBeta, TokenPlatform platform)
1112
{
1213
DateTimeOffset now = this._time.Now;
13-
bool isBeta = game == TokenGame.BetaBuild;
14-
IEnumerable<PinProgressRelation> existingProgresses = this.GetPinProgressesByUser(user, isBeta);
14+
IEnumerable<PinProgressRelation> existingProgresses = this.GetPinProgressesByUser(user, isBeta, platform);
1515
List<long> descendingProgressPins =
1616
[
1717
(long)ServerPins.TopXOfAnyStoryLevelWithOver50Scores,
@@ -34,6 +34,7 @@ public void UpdateUserPinProgress(Dictionary<long, int> pinProgressUpdates, Game
3434
FirstPublished = now,
3535
LastUpdated = now,
3636
IsBeta = isBeta,
37+
Platform = platform,
3738
};
3839
this.PinProgressRelations.Add(newRelation);
3940
continue;
@@ -53,10 +54,12 @@ public void UpdateUserPinProgress(Dictionary<long, int> pinProgressUpdates, Game
5354
this.SaveChanges();
5455
}
5556

56-
public void UpdateUserProfilePins(List<long> pinUpdates, GameUser user, TokenGame game)
57+
public void UpdateUserProfilePins(List<long> pinUpdates, GameUser user, TokenGame game, TokenPlatform platform)
5758
{
58-
IEnumerable<long> existingProgressIds = this.GetPinProgressesByUser(user, game == TokenGame.BetaBuild).Select(p => p.PinId);
59-
IEnumerable<ProfilePinRelation> existingProfilePins = this.GetProfilePinsByUser(user, game);
59+
IEnumerable<long> existingProgressIds = this
60+
.GetPinProgressesByUser(user, game == TokenGame.BetaBuild, platform)
61+
.Select(p => p.PinId);
62+
IEnumerable<ProfilePinRelation> existingProfilePins = this.GetProfilePinsByUser(user, game, platform);
6063
DateTimeOffset now = this._time.Now;
6164

6265
for (int i = 0; i < pinUpdates.Count; i++)
@@ -80,6 +83,7 @@ public void UpdateUserProfilePins(List<long> pinUpdates, GameUser user, TokenGam
8083
PublisherId = user.UserId,
8184
Index = i,
8285
Game = game,
86+
Platform = platform,
8387
Timestamp = now,
8488
});
8589
}
@@ -93,10 +97,10 @@ public void UpdateUserProfilePins(List<long> pinUpdates, GameUser user, TokenGam
9397
this.SaveChanges();
9498
}
9599

96-
public PinProgressRelation UpdateUserPinProgressToLowest(long pinId, int newProgressValue, GameUser user, bool isBeta)
100+
public PinProgressRelation UpdateUserPinProgressToLowest(long pinId, int newProgressValue, GameUser user, bool isBeta, TokenPlatform platform)
97101
{
98102
// Get pin progress if it exists already
99-
PinProgressRelation? progressToUpdate = this.GetUserPinProgress(pinId, user, isBeta);
103+
PinProgressRelation? progressToUpdate = this.GetUserPinProgress(pinId, user, isBeta, platform);
100104
DateTimeOffset now = this._time.Now;
101105

102106
if (progressToUpdate == null)
@@ -126,31 +130,15 @@ public PinProgressRelation UpdateUserPinProgressToLowest(long pinId, int newProg
126130
return progressToUpdate!;
127131
}
128132

129-
public void IncrementUserPinProgress(long pinId, int progressToAdd, GameUser user)
130-
{
131-
this.IncrementUserPinProgressInternal(pinId, progressToAdd, user, true);
132-
this.IncrementUserPinProgressInternal(pinId, progressToAdd, user, false);
133-
134-
this.SaveChanges();
135-
}
136-
137-
public PinProgressRelation IncrementUserPinProgress(long pinId, int progressToAdd, GameUser user, bool isBeta)
138-
{
139-
PinProgressRelation relation = this.IncrementUserPinProgressInternal(pinId, progressToAdd, user, isBeta);
140-
this.SaveChanges();
141-
142-
return relation;
143-
}
144-
145-
private PinProgressRelation IncrementUserPinProgressInternal(long pinId, int progressToAdd, GameUser user, bool isBeta)
133+
public PinProgressRelation IncrementUserPinProgress(long pinId, int progressToAdd, GameUser user, bool isBeta, TokenPlatform platform)
146134
{
147135
// Get pin progress if it exists already
148-
PinProgressRelation? progressToUpdate = this.GetUserPinProgress(pinId, user, isBeta);
136+
PinProgressRelation? progressToUpdate = this.GetUserPinProgress(pinId, user, isBeta, platform);
149137
DateTimeOffset now = this._time.Now;
150138

151139
if (progressToUpdate == null)
152140
{
153-
PinProgressRelation newRelation = new()
141+
progressToUpdate = new()
154142
{
155143
PinId = pinId,
156144
Progress = progressToAdd,
@@ -159,36 +147,53 @@ private PinProgressRelation IncrementUserPinProgressInternal(long pinId, int pro
159147
FirstPublished = now,
160148
LastUpdated = now,
161149
IsBeta = isBeta,
150+
Platform = platform,
162151
};
163-
164-
this.PinProgressRelations.Add(newRelation);
165-
return newRelation;
152+
this.PinProgressRelations.Add(progressToUpdate);
166153
}
167154
else
168155
{
169156
progressToUpdate.Progress += progressToAdd;
170157
progressToUpdate.LastUpdated = now;
171158
}
172159

160+
this.SaveChanges();
173161
return progressToUpdate;
174162
}
175163

176-
private IEnumerable<PinProgressRelation> GetPinProgressesByUser(GameUser user, bool isBeta)
164+
private IEnumerable<PinProgressRelation> GetPinProgressesByUser(GameUser user, bool isBeta, TokenPlatform platform)
177165
=> this.PinProgressRelations
178-
.Where(p => p.PublisherId == user.UserId && p.IsBeta == isBeta)
166+
.Where(p => p.PublisherId == user.UserId && (p.IsBeta == isBeta && p.Platform == platform || p.Platform == TokenPlatform.Website))
179167
.OrderByDescending(p => p.LastUpdated);
180168

181-
public DatabaseList<PinProgressRelation> GetPinProgressesByUser(GameUser user, TokenGame game, int skip, int count)
182-
=> new(this.GetPinProgressesByUser(user, game == TokenGame.BetaBuild), skip, count);
169+
public DatabaseList<PinProgressRelation> GetPinProgressesByUser(GameUser user, bool isBeta, TokenPlatform platform, int skip, int count)
170+
=> new(this.GetPinProgressesByUser(user, isBeta, platform), skip, count);
183171

184-
public PinProgressRelation? GetUserPinProgress(long pinId, GameUser user, bool isBeta)
185-
=> this.PinProgressRelations.FirstOrDefault(p => p.PinId == pinId && p.PublisherId == user.UserId && p.IsBeta == isBeta);
172+
public PinProgressRelation? GetUserPinProgress(long pinId, GameUser user, bool isBeta, TokenPlatform platform)
173+
=> this.PinProgressRelations.FirstOrDefault(p => p.PinId == pinId && p.PublisherId == user.UserId
174+
&& (p.IsBeta == isBeta && p.Platform == platform || p.Platform == TokenPlatform.Website));
186175

187-
private IEnumerable<ProfilePinRelation> GetProfilePinsByUser(GameUser user, TokenGame game)
176+
private IEnumerable<ProfilePinRelation> GetProfilePinsByUser(GameUser user, TokenGame game, TokenPlatform platform)
188177
=> this.ProfilePinRelations
189-
.Where(p => p.PublisherId == user.UserId && p.Game == game)
178+
.Where(p => p.PublisherId == user.UserId && p.Game == game && p.Platform == platform)
190179
.OrderBy(p => p.Index);
191180

192-
public DatabaseList<ProfilePinRelation> GetProfilePinsByUser(GameUser user, TokenGame game, int skip, int count)
193-
=> new(this.GetProfilePinsByUser(user, game), skip, count);
181+
public DatabaseList<ProfilePinRelation> GetProfilePinsByUser(GameUser user, TokenGame game, TokenPlatform platform, int skip, int count)
182+
=> new(this.GetProfilePinsByUser(user, game, platform), skip, count);
183+
184+
#region Migration Methods
185+
186+
public void AddPinProgress(PinProgressRelation relation, bool save)
187+
{
188+
this.PinProgressRelations.Add(relation);
189+
if (save) this.SaveChanges();
190+
}
191+
192+
public void RemoveAllPinProgressesByIdAndUser(long pinId, ObjectId userId, bool save)
193+
{
194+
this.PinProgressRelations.RemoveRange(p => p.PinId == pinId && p.PublisherId == userId);
195+
if (save) this.SaveChanges();
196+
}
197+
198+
#endregion
194199
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using Microsoft.EntityFrameworkCore.Infrastructure;
2+
using Microsoft.EntityFrameworkCore.Migrations;
3+
using Refresh.Database.Models.Authentication;
4+
using Refresh.Database.Models.Pins;
5+
6+
#nullable disable
7+
8+
namespace Refresh.Database.Migrations
9+
{
10+
/// <inheritdoc />
11+
[DbContext(typeof(GameDatabaseContext))]
12+
[Migration("20251029184346_SeperatePinProgressByPlatform")]
13+
public partial class SeperatePinProgressByPlatform : Migration
14+
{
15+
/// <inheritdoc />
16+
protected override void Up(MigrationBuilder migrationBuilder)
17+
{
18+
// RPCS3 is the safest default value here, as game achievements on RPCS3 are considered the least valuable.
19+
// Also, the game will sync progress and profile pins after login anyway, so practically no information is lost here.
20+
migrationBuilder.AddColumn<int>(
21+
name: "Platform",
22+
table: "ProfilePinRelations",
23+
type: "integer",
24+
nullable: false,
25+
defaultValue: TokenPlatform.RPCS3);
26+
27+
migrationBuilder.AddColumn<int>(
28+
name: "Platform",
29+
table: "PinProgressRelations",
30+
type: "integer",
31+
nullable: false,
32+
defaultValue: TokenPlatform.RPCS3);
33+
34+
migrationBuilder.DropPrimaryKey(
35+
name: "PK_ProfilePinRelations",
36+
table: "ProfilePinRelations");
37+
38+
migrationBuilder.DropPrimaryKey(
39+
name: "PK_PinProgressRelations",
40+
table: "PinProgressRelations");
41+
42+
migrationBuilder.AddPrimaryKey(
43+
name: "PK_ProfilePinRelations",
44+
table: "ProfilePinRelations",
45+
columns: ["Index", "PublisherId", "Game", "Platform"]);
46+
47+
migrationBuilder.AddPrimaryKey(
48+
name: "PK_PinProgressRelations",
49+
table: "PinProgressRelations",
50+
columns: ["PinId", "PublisherId", "IsBeta", "Platform"]);
51+
}
52+
53+
/// <inheritdoc />
54+
protected override void Down(MigrationBuilder migrationBuilder)
55+
{
56+
migrationBuilder.DropColumn(
57+
name: "Platform",
58+
table: "ProfilePinRelations");
59+
60+
migrationBuilder.DropColumn(
61+
name: "Platform",
62+
table: "PinProgressRelations");
63+
64+
migrationBuilder.DropPrimaryKey(
65+
name: "PK_ProfilePinRelations",
66+
table: "ProfilePinRelations");
67+
68+
migrationBuilder.DropPrimaryKey(
69+
name: "PK_PinProgressRelations",
70+
table: "PinProgressRelations");
71+
72+
migrationBuilder.AddPrimaryKey(
73+
name: "PK_ProfilePinRelations",
74+
table: "ProfilePinRelations",
75+
columns: ["Index", "PublisherId", "Game"]);
76+
77+
migrationBuilder.AddPrimaryKey(
78+
name: "PK_PinProgressRelations",
79+
table: "PinProgressRelations",
80+
columns: ["PinId", "PublisherId", "IsBeta"]);
81+
}
82+
}
83+
}

Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
980980
b.Property<bool>("IsBeta")
981981
.HasColumnType("boolean");
982982

983+
b.Property<int>("Platform")
984+
.HasColumnType("integer");
985+
983986
b.Property<DateTimeOffset>("FirstPublished")
984987
.HasColumnType("timestamp with time zone");
985988

@@ -989,7 +992,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
989992
b.Property<int>("Progress")
990993
.HasColumnType("integer");
991994

992-
b.HasKey("PinId", "PublisherId", "IsBeta");
995+
b.HasKey("PinId", "PublisherId", "IsBeta", "Platform");
993996

994997
b.HasIndex("PublisherId");
995998

@@ -1061,13 +1064,16 @@ protected override void BuildModel(ModelBuilder modelBuilder)
10611064
b.Property<int>("Game")
10621065
.HasColumnType("integer");
10631066

1067+
b.Property<int>("Platform")
1068+
.HasColumnType("integer");
1069+
10641070
b.Property<long>("PinId")
10651071
.HasColumnType("bigint");
10661072

10671073
b.Property<DateTimeOffset>("Timestamp")
10681074
.HasColumnType("timestamp with time zone");
10691075

1070-
b.HasKey("Index", "PublisherId", "Game");
1076+
b.HasKey("Index", "PublisherId", "Game", "Platform");
10711077

10721078
b.HasIndex("PublisherId");
10731079

Refresh.Database/Models/Relations/PinProgressRelation.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using MongoDB.Bson;
2+
using Refresh.Database.Models.Authentication;
23
using Refresh.Database.Models.Users;
34

45
namespace Refresh.Database.Models.Relations;
56

67
#nullable disable
78

8-
[PrimaryKey(nameof(PinId), nameof(PublisherId), nameof(IsBeta))]
9+
[PrimaryKey(nameof(PinId), nameof(PublisherId), nameof(IsBeta), nameof(Platform))]
910
public partial class PinProgressRelation
1011
{
1112
/// <summary>
@@ -26,4 +27,13 @@ public partial class PinProgressRelation
2627
/// for these game groups seperately.
2728
/// </summary>
2829
public bool IsBeta { get; set; }
30+
31+
/// <summary>
32+
/// Seperates pin progresses per platform, to not let progress transfer inbetween PS3, RPCS3 and especially Vita,
33+
/// as that's commonly unwanted behaviour.
34+
///
35+
/// Website = this pin is "universal" across platforms and games (e.g. LBP.me pin).
36+
/// In that case ignore both IsBeta and Platform, and show this progress on all games.
37+
/// </summary>
38+
public TokenPlatform Platform { get; set; }
2939
}

Refresh.Database/Models/Relations/ProfilePinRelation.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Refresh.Database.Models.Relations;
66

77
#nullable disable
88

9-
[PrimaryKey(nameof(Index), nameof(PublisherId), nameof(Game))]
9+
[PrimaryKey(nameof(Index), nameof(PublisherId), nameof(Game), nameof(Platform))]
1010
public partial class ProfilePinRelation
1111
{
1212
public long PinId { get; set; }
@@ -26,5 +26,10 @@ public partial class ProfilePinRelation
2626
/// </summary>
2727
public TokenGame Game { get; set; }
2828

29+
/// <summary>
30+
/// Since pin progresses are split per platforms now, we also have to split profile pins aswell
31+
/// </summary>
32+
public TokenPlatform Platform { get; set; }
33+
2934
public DateTimeOffset Timestamp { get; set; }
3035
}

Refresh.Interfaces.APIv3/Endpoints/AuthenticationApiEndpoints.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public ApiResponse<IApiAuthenticationResponse> Authenticate(RequestContext conte
109109
context.Logger.LogInfo(BunkumCategory.Authentication, $"{user} successfully logged in through the API");
110110

111111
// Update pin progress for signing into the API
112-
database.IncrementUserPinProgress((long)ServerPins.SignIntoWebsite, 1, user);
112+
database.IncrementUserPinProgress((long)ServerPins.SignIntoWebsite, 1, user, false, TokenPlatform.Website);
113113

114114
return new ApiAuthenticationResponse
115115
{

Refresh.Interfaces.APIv3/Endpoints/LevelApiEndpoints.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Refresh.Core.Services;
1010
using Refresh.Core.Types.Data;
1111
using Refresh.Database;
12+
using Refresh.Database.Models.Authentication;
1213
using Refresh.Database.Models.Levels;
1314
using Refresh.Database.Models.Pins;
1415
using Refresh.Database.Models.Users;
@@ -183,7 +184,7 @@ public ApiOkResponse QueueLevel(RequestContext context, GameDatabaseContext data
183184
database.QueueLevel(level, user);
184185

185186
// Update pin progress for queueing a level through the API
186-
database.IncrementUserPinProgress((long)ServerPins.QueueLevelOnWebsite, 1, user);
187+
database.IncrementUserPinProgress((long)ServerPins.QueueLevelOnWebsite, 1, user, false, TokenPlatform.Website);
187188

188189
return new ApiOkResponse();
189190
}

Refresh.Interfaces.Game/Endpoints/DataTypes/Response/GameUserResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public class GameUserResponse : IDataConvertableFrom<GameUserResponse, GameUser>
121121
}
122122
if (game is not TokenGame.LittleBigPlanet1 or TokenGame.LittleBigPlanetPSP)
123123
{
124-
response.ProfilePins = dataContext.Database.GetProfilePinsByUser(old, dataContext.Game, 0, 3)
124+
response.ProfilePins = dataContext.Database.GetProfilePinsByUser(old, dataContext.Game, dataContext.Platform, 0, 3)
125125
.Items.Select(p => p.PinId).ToList();
126126
}
127127

0 commit comments

Comments
 (0)