Skip to content

Commit a965fbf

Browse files
authored
DB leaderboards (#66)
* DB leaderboards initial commit * Clarify message * uuuhhh AMONG US
1 parent 6896760 commit a965fbf

9 files changed

Lines changed: 1177 additions & 84 deletions

‎ReplayBrowser/Data/Migrations/20241111013501_DbLeaderboards.Designer.cs‎

Lines changed: 718 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Microsoft.EntityFrameworkCore.Migrations;
4+
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
5+
6+
#nullable disable
7+
8+
namespace Server.Migrations
9+
{
10+
/// <inheritdoc />
11+
public partial class DbLeaderboards : Migration
12+
{
13+
/// <inheritdoc />
14+
protected override void Up(MigrationBuilder migrationBuilder)
15+
{
16+
migrationBuilder.CreateTable(
17+
name: "LeaderboardDefinitions",
18+
columns: table => new
19+
{
20+
Name = table.Column<string>(type: "text", nullable: false),
21+
TrackedData = table.Column<string>(type: "text", nullable: false),
22+
ExtraInfo = table.Column<string>(type: "text", nullable: true),
23+
NameColumn = table.Column<string>(type: "text", nullable: false)
24+
},
25+
constraints: table =>
26+
{
27+
table.PrimaryKey("PK_LeaderboardDefinitions", x => x.Name);
28+
});
29+
30+
migrationBuilder.CreateTable(
31+
name: "Leaderboards",
32+
columns: table => new
33+
{
34+
Id = table.Column<int>(type: "integer", nullable: false)
35+
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
36+
Servers = table.Column<List<string>>(type: "text[]", nullable: false),
37+
Count = table.Column<int>(type: "integer", nullable: false),
38+
Position = table.Column<int>(type: "integer", nullable: false),
39+
PlayerGuid = table.Column<Guid>(type: "uuid", nullable: true),
40+
Username = table.Column<string>(type: "text", nullable: false),
41+
LeaderboardDefinitionName = table.Column<string>(type: "text", nullable: false)
42+
},
43+
constraints: table =>
44+
{
45+
table.PrimaryKey("PK_Leaderboards", x => x.Id);
46+
table.ForeignKey(
47+
name: "FK_Leaderboards_LeaderboardDefinitions_LeaderboardDefinitionNa~",
48+
column: x => x.LeaderboardDefinitionName,
49+
principalTable: "LeaderboardDefinitions",
50+
principalColumn: "Name",
51+
onDelete: ReferentialAction.Cascade);
52+
});
53+
54+
migrationBuilder.CreateIndex(
55+
name: "IX_Leaderboards_LeaderboardDefinitionName",
56+
table: "Leaderboards",
57+
column: "LeaderboardDefinitionName");
58+
}
59+
60+
/// <inheritdoc />
61+
protected override void Down(MigrationBuilder migrationBuilder)
62+
{
63+
migrationBuilder.DropTable(
64+
name: "Leaderboards");
65+
66+
migrationBuilder.DropTable(
67+
name: "LeaderboardDefinitions");
68+
}
69+
}
70+
}

‎ReplayBrowser/Data/Migrations/ReplayDbContextModelSnapshot.cs‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,63 @@ protected override void BuildModel(ModelBuilder modelBuilder)
301301
b.ToTable("JobDepartments");
302302
});
303303

304+
modelBuilder.Entity("ReplayBrowser.Data.Models.LeaderboardDefinition", b =>
305+
{
306+
b.Property<string>("Name")
307+
.HasColumnType("text");
308+
309+
b.Property<string>("ExtraInfo")
310+
.HasColumnType("text");
311+
312+
b.Property<string>("NameColumn")
313+
.IsRequired()
314+
.HasColumnType("text");
315+
316+
b.Property<string>("TrackedData")
317+
.IsRequired()
318+
.HasColumnType("text");
319+
320+
b.HasKey("Name");
321+
322+
b.ToTable("LeaderboardDefinitions");
323+
});
324+
325+
modelBuilder.Entity("ReplayBrowser.Data.Models.LeaderboardPosition", b =>
326+
{
327+
b.Property<int>("Id")
328+
.ValueGeneratedOnAdd()
329+
.HasColumnType("integer");
330+
331+
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
332+
333+
b.Property<int>("Count")
334+
.HasColumnType("integer");
335+
336+
b.Property<string>("LeaderboardDefinitionName")
337+
.IsRequired()
338+
.HasColumnType("text");
339+
340+
b.Property<Guid?>("PlayerGuid")
341+
.HasColumnType("uuid");
342+
343+
b.Property<int>("Position")
344+
.HasColumnType("integer");
345+
346+
b.Property<List<string>>("Servers")
347+
.IsRequired()
348+
.HasColumnType("text[]");
349+
350+
b.Property<string>("Username")
351+
.IsRequired()
352+
.HasColumnType("text");
353+
354+
b.HasKey("Id");
355+
356+
b.HasIndex("LeaderboardDefinitionName");
357+
358+
b.ToTable("Leaderboards");
359+
});
360+
304361
modelBuilder.Entity("ReplayBrowser.Data.Models.Notice", b =>
305362
{
306363
b.Property<int?>("Id")
@@ -585,6 +642,17 @@ protected override void BuildModel(ModelBuilder modelBuilder)
585642
.HasForeignKey("CollectedPlayerDataPlayerGuid");
586643
});
587644

645+
modelBuilder.Entity("ReplayBrowser.Data.Models.LeaderboardPosition", b =>
646+
{
647+
b.HasOne("ReplayBrowser.Data.Models.LeaderboardDefinition", "LeaderboardDefinition")
648+
.WithMany()
649+
.HasForeignKey("LeaderboardDefinitionName")
650+
.OnDelete(DeleteBehavior.Cascade)
651+
.IsRequired();
652+
653+
b.Navigation("LeaderboardDefinition");
654+
});
655+
588656
modelBuilder.Entity("ReplayBrowser.Data.Models.Player", b =>
589657
{
590658
b.HasOne("ReplayBrowser.Data.Models.JobDepartment", "EffectiveJob")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
3+
4+
namespace ReplayBrowser.Data.Models;
5+
6+
public class LeaderboardDefinition : IEntityTypeConfiguration<LeaderboardDefinition>
7+
{
8+
public required string Name { get; set; }
9+
10+
/// <summary>
11+
/// The text that will appear for the "Count" column.
12+
/// </summary>
13+
public required string TrackedData { get; set; }
14+
15+
/// <summary>
16+
/// Will be displayed in a small font below the name.
17+
/// </summary>
18+
public string? ExtraInfo { get; set; }
19+
public string NameColumn { get; set; } = "Player Name";
20+
21+
public void Configure(EntityTypeBuilder<LeaderboardDefinition> builder)
22+
{
23+
builder.HasKey(x => x.Name);
24+
builder.Property(x => x.TrackedData).IsRequired();
25+
builder.Property(x => x.NameColumn).IsRequired();
26+
}
27+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
3+
4+
namespace ReplayBrowser.Data.Models;
5+
6+
public class LeaderboardPosition : IEntityTypeConfiguration<LeaderboardPosition>
7+
{
8+
public int Id { get; set; }
9+
10+
/// <summary>
11+
/// The servers that this position is for.
12+
/// </summary>
13+
public required List<string> Servers { get; set; } = null!;
14+
15+
/// <summary>
16+
/// The number next to the position. For example "most times played" would be a count of how many times the player has played.
17+
/// </summary>
18+
public required int Count { get; set; }
19+
20+
/// <summary>
21+
/// The position of the player in the leaderboard.
22+
/// </summary>
23+
public int Position { get; set; }
24+
25+
/// <summary>
26+
/// The GUID of the player. If null, position is not for a player, but rather a general statistic.
27+
/// </summary>
28+
public Guid? PlayerGuid { get; set; }
29+
30+
/// <summary>
31+
/// The display value of the player or statistic.
32+
/// </summary>
33+
public string Username { get; set; } = null!;
34+
35+
36+
public string LeaderboardDefinitionName { get; set; } = null!;
37+
public LeaderboardDefinition LeaderboardDefinition { get; set; } = null!;
38+
39+
40+
41+
public void Configure(EntityTypeBuilder<LeaderboardPosition> builder)
42+
{
43+
builder.HasOne(lp => lp.LeaderboardDefinition)
44+
.WithMany()
45+
.HasForeignKey(lp => lp.LeaderboardDefinitionName);
46+
47+
builder.HasKey(x => x.Id);
48+
builder.Property(x => x.Servers).IsRequired();
49+
builder.Property(x => x.Count).IsRequired();
50+
builder.Property(x => x.Position).IsRequired();
51+
builder.Property(x => x.PlayerGuid).IsRequired(false);
52+
builder.Property(x => x.Username).IsRequired();
53+
}
54+
}

‎ReplayBrowser/Data/ReplayDbContext.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
4949
public DbSet<ReplayParticipant> ReplayParticipants { get; set; }
5050

5151
public DbSet<ServerToken> ServerTokens { get; set; }
52+
53+
public DbSet<LeaderboardPosition> Leaderboards { get; set; }
54+
public DbSet<LeaderboardDefinition> LeaderboardDefinitions { get; set; }
5255
}

‎ReplayBrowser/Models/LeaderboardData.cs‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,6 @@
33

44
namespace ReplayBrowser.Models;
55

6-
public class LeaderboardData
7-
{
8-
public bool IsCache { get; set; } = false;
9-
10-
public List<Leaderboard> Leaderboards { get; set; } = null!;
11-
}
12-
136
public class PlayerCount
147
{
158
public PlayerData? Player { get; set; }

‎ReplayBrowser/Pages/Leaderboard.razor‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@page "/leaderboard"
2+
@using Humanizer
23
@using Microsoft.AspNetCore.Components.Authorization
34
@using ReplayBrowser.Models
45
@using ReplayBrowser.Services
@@ -20,6 +21,14 @@
2021

2122
<PageTitle>Leaderboard</PageTitle>
2223
<h4>Leaderboards</h4>
24+
<em>Leaderboards update every 24 hours.</em>
25+
@if(LeaderboardService.IsUpdating) {
26+
<div class="alert alert-warning" role="alert">
27+
<p>The leaderboards are currently updating. This may take a while. Please be patient.</p>
28+
<em>Update started: @LeaderboardService.UpdateStarted.Humanize(utcDate: true). Current progress: @LeaderboardService.UpdateProgress / @LeaderboardService.UpdateTotal</em>
29+
</div>
30+
}
31+
2332
@if(RequestedPrivate)
2433
{
2534
<div class="alert alert-danger" role="alert">
@@ -105,7 +114,14 @@ else
105114

106115
<hr/>
107116

108-
@foreach (var leaderboard in LeaderboardData.Leaderboards)
117+
@if(LeaderboardData.Length == 0)
118+
{
119+
<div class="alert alert-warning" role="alert">
120+
<p>No data was found for the selected servers and time range. Data is probably still collected and calculated. Please try again later. This will take a long time.</p>
121+
</div>
122+
}
123+
124+
@foreach (var leaderboard in LeaderboardData)
109125
{
110126
<h4>@leaderboard.Name</h4>
111127
if (leaderboard.ExtraInfo != null)
@@ -233,7 +249,7 @@ else
233249

234250
@code{
235251
private bool IsLoading { get; set; } = true;
236-
private LeaderboardData? LeaderboardData { get; set; } = null;
252+
private Models.Leaderboard[]? LeaderboardData { get; set; } = null;
237253
private bool RequestedPrivate { get; set; } = false;
238254

239255
protected override async Task OnInitializedAsync()
@@ -267,7 +283,7 @@ else
267283
}
268284
}
269285

270-
LeaderboardData? leaderboard = null;
286+
Models.Leaderboard[]? leaderboard = null;
271287
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
272288
try
273289
{
@@ -279,7 +295,7 @@ else
279295
}
280296

281297
var selectedServersArray = selectedServers.Split(',');
282-
leaderboard = await LeaderboardService.GetLeaderboard(timeRangeEnum, username, selectedServersArray, authState, entries);
298+
leaderboard = await LeaderboardService.GetLeaderboards(timeRangeEnum, username, selectedServersArray, authState, entries);
283299
}
284300
catch (UnauthorizedAccessException)
285301
{

0 commit comments

Comments
 (0)