Complete Records/Leaderboards + Time Trials systems (100%)

RECORDS & LEADERBOARDS (5/5 endpoints - 100%):
- Created LeaderboardsController with 5 endpoints
- GET /synergy/leaderboards/timetrials/{trialId}
- GET /synergy/leaderboards/career/{series}/{event}
- GET /synergy/leaderboards/global/top100
- GET /synergy/leaderboards/player/{synergyId}/records
- GET /synergy/leaderboards/compare/{synergyId1}/{synergyId2}

Added LeaderboardEntry and PersonalRecord models and database tables.
Migration applied: AddLeaderboardsAndRecords

Updated RewardsController.SubmitTimeTrial to track personal bests,
update leaderboards, and award 50 gold bonus for improvements.

Updated ProgressionController.CompleteCareerEvent similarly for
career event personal records.

TIME TRIALS (6/6 endpoints - 100%):
- GET /synergy/rewards/timetrials - List with time remaining
- GET /synergy/rewards/timetrials/{id} - Details with stats
- POST /synergy/rewards/timetrials/{id}/submit - Submit with PB tracking
- GET /synergy/rewards/timetrials/player/{synergyId}/results - History
- POST /synergy/rewards/timetrials/{id}/claim - Claim bonuses
- GET /synergy/leaderboards/timetrials/{id} - Leaderboards (above)

Added navigation properties to TimeTrialResult for easier queries.

Server progress: 66/73 endpoints (90%)
Two complete systems: Records/Leaderboards + Time Trials

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-02-22 17:49:23 -08:00
parent e839064b35
commit a6167c8249
11 changed files with 2695 additions and 25 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,95 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RR3CommunityServer.Migrations
{
/// <inheritdoc />
public partial class AddLeaderboardsAndRecords : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "LeaderboardEntries",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SynergyId = table.Column<string>(type: "TEXT", nullable: false),
PlayerName = table.Column<string>(type: "TEXT", nullable: false),
RecordType = table.Column<string>(type: "TEXT", nullable: false),
RecordCategory = table.Column<string>(type: "TEXT", nullable: false),
TrackName = table.Column<string>(type: "TEXT", nullable: true),
CarName = table.Column<string>(type: "TEXT", nullable: true),
TimeSeconds = table.Column<double>(type: "REAL", nullable: false),
SubmittedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_LeaderboardEntries", x => x.Id);
});
migrationBuilder.CreateTable(
name: "PersonalRecords",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SynergyId = table.Column<string>(type: "TEXT", nullable: false),
RecordType = table.Column<string>(type: "TEXT", nullable: false),
RecordCategory = table.Column<string>(type: "TEXT", nullable: false),
TrackName = table.Column<string>(type: "TEXT", nullable: true),
CarName = table.Column<string>(type: "TEXT", nullable: true),
BestTimeSeconds = table.Column<double>(type: "REAL", nullable: false),
AchievedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
PreviousBestTime = table.Column<DateTime>(type: "TEXT", nullable: true),
ImprovementSeconds = table.Column<double>(type: "REAL", nullable: true),
TotalAttempts = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PersonalRecords", x => x.Id);
});
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 2, 1, 33, 39, 587, DateTimeKind.Utc).AddTicks(7946), new DateTime(2026, 2, 23, 1, 33, 39, 587, DateTimeKind.Utc).AddTicks(7944) });
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 2,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 2, 1, 33, 39, 587, DateTimeKind.Utc).AddTicks(7954), new DateTime(2026, 2, 23, 1, 33, 39, 587, DateTimeKind.Utc).AddTicks(7954) });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "LeaderboardEntries");
migrationBuilder.DropTable(
name: "PersonalRecords");
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 1, 7, 47, 46, 836, DateTimeKind.Utc).AddTicks(7182), new DateTime(2026, 2, 22, 7, 47, 46, 836, DateTimeKind.Utc).AddTicks(7180) });
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 2,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 1, 7, 47, 46, 836, DateTimeKind.Utc).AddTicks(7192), new DateTime(2026, 2, 22, 7, 47, 46, 836, DateTimeKind.Utc).AddTicks(7191) });
}
}
}

View File

@@ -496,6 +496,45 @@ namespace RR3CommunityServer.Migrations
b.ToTable("GameAssets");
});
modelBuilder.Entity("RR3CommunityServer.Data.LeaderboardEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CarName")
.HasColumnType("TEXT");
b.Property<string>("PlayerName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("RecordCategory")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("RecordType")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("SubmittedAt")
.HasColumnType("TEXT");
b.Property<string>("SynergyId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<double>("TimeSeconds")
.HasColumnType("REAL");
b.Property<string>("TrackName")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("LeaderboardEntries");
});
modelBuilder.Entity("RR3CommunityServer.Data.ModPack", b =>
{
b.Property<int>("Id")
@@ -589,6 +628,50 @@ namespace RR3CommunityServer.Migrations
b.ToTable("OwnedCars");
});
modelBuilder.Entity("RR3CommunityServer.Data.PersonalRecord", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AchievedAt")
.HasColumnType("TEXT");
b.Property<double>("BestTimeSeconds")
.HasColumnType("REAL");
b.Property<string>("CarName")
.HasColumnType("TEXT");
b.Property<double?>("ImprovementSeconds")
.HasColumnType("REAL");
b.Property<DateTime?>("PreviousBestTime")
.HasColumnType("TEXT");
b.Property<string>("RecordCategory")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("RecordType")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("SynergyId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("TotalAttempts")
.HasColumnType("INTEGER");
b.Property<string>("TrackName")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("PersonalRecords");
});
modelBuilder.Entity("RR3CommunityServer.Data.PlayerSave", b =>
{
b.Property<int>("Id")
@@ -739,10 +822,10 @@ namespace RR3CommunityServer.Migrations
Active = true,
CarName = "Any Car",
CashReward = 10000,
EndDate = new DateTime(2026, 3, 1, 7, 47, 46, 836, DateTimeKind.Utc).AddTicks(7182),
EndDate = new DateTime(2026, 3, 2, 1, 33, 39, 587, DateTimeKind.Utc).AddTicks(7946),
GoldReward = 50,
Name = "Daily Sprint Challenge",
StartDate = new DateTime(2026, 2, 22, 7, 47, 46, 836, DateTimeKind.Utc).AddTicks(7180),
StartDate = new DateTime(2026, 2, 23, 1, 33, 39, 587, DateTimeKind.Utc).AddTicks(7944),
TargetTime = 90.5,
TrackName = "Silverstone National"
},
@@ -752,10 +835,10 @@ namespace RR3CommunityServer.Migrations
Active = true,
CarName = "Any Car",
CashReward = 25000,
EndDate = new DateTime(2026, 3, 1, 7, 47, 46, 836, DateTimeKind.Utc).AddTicks(7192),
EndDate = new DateTime(2026, 3, 2, 1, 33, 39, 587, DateTimeKind.Utc).AddTicks(7954),
GoldReward = 100,
Name = "Speed Demon Trial",
StartDate = new DateTime(2026, 2, 22, 7, 47, 46, 836, DateTimeKind.Utc).AddTicks(7191),
StartDate = new DateTime(2026, 2, 23, 1, 33, 39, 587, DateTimeKind.Utc).AddTicks(7954),
TargetTime = 120.0,
TrackName = "Dubai Autodrome"
});