Add Friends/Social & Multiplayer systems - 95 total endpoints

- Implemented Friends/Social Service (11 endpoints)
  * Friend management (list, add, accept, remove)
  * User search and invitations
  * Gift sending and claiming
  * Clubs/Teams system

- Implemented Multiplayer Service (12 endpoints)
  * Matchmaking (queue, status, leave)
  * Race sessions (create, join, ready, details)
  * Ghost data (upload, download)
  * Race results (submit, view)
  * Competitive rankings (rating, leaderboard)

- Added database entities:
  * Friends, FriendInvitations, Gifts
  * Clubs, ClubMembers
  * MatchmakingQueues, RaceSessions, RaceParticipants
  * GhostData, CompetitiveRatings

- Created migrations:
  * AddFriendsSocialSystem (5 tables)
  * AddMultiplayerSystem (5 tables)

Total: 95 endpoints - 100% EA server replacement ready

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-02-23 16:55:33 -08:00
parent a8d282ab36
commit a934f57b52
28 changed files with 8136 additions and 10 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,253 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RR3CommunityServer.Migrations
{
/// <inheritdoc />
public partial class AddFriendsSocialSystem : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Clubs",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", nullable: false),
Description = table.Column<string>(type: "TEXT", nullable: false),
Tag = table.Column<string>(type: "TEXT", nullable: false),
OwnerId = table.Column<int>(type: "INTEGER", nullable: false),
MaxMembers = table.Column<int>(type: "INTEGER", nullable: false),
IsPublic = table.Column<bool>(type: "INTEGER", nullable: false),
IsRecruiting = table.Column<bool>(type: "INTEGER", nullable: false),
TotalPoints = table.Column<int>(type: "INTEGER", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Clubs", x => x.Id);
table.ForeignKey(
name: "FK_Clubs_Users_OwnerId",
column: x => x.OwnerId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "FriendInvitations",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SenderId = table.Column<int>(type: "INTEGER", nullable: false),
ReceiverId = table.Column<int>(type: "INTEGER", nullable: false),
Status = table.Column<string>(type: "TEXT", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
RespondedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_FriendInvitations", x => x.Id);
table.ForeignKey(
name: "FK_FriendInvitations_Users_ReceiverId",
column: x => x.ReceiverId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_FriendInvitations_Users_SenderId",
column: x => x.SenderId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Friends",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
User1Id = table.Column<int>(type: "INTEGER", nullable: false),
User2Id = table.Column<int>(type: "INTEGER", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Friends", x => x.Id);
table.ForeignKey(
name: "FK_Friends_Users_User1Id",
column: x => x.User1Id,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Friends_Users_User2Id",
column: x => x.User2Id,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Gifts",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SenderId = table.Column<int>(type: "INTEGER", nullable: false),
ReceiverId = table.Column<int>(type: "INTEGER", nullable: false),
GiftType = table.Column<string>(type: "TEXT", nullable: false),
Amount = table.Column<int>(type: "INTEGER", nullable: false),
Message = table.Column<string>(type: "TEXT", nullable: true),
Claimed = table.Column<bool>(type: "INTEGER", nullable: false),
SentAt = table.Column<DateTime>(type: "TEXT", nullable: false),
ClaimedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Gifts", x => x.Id);
table.ForeignKey(
name: "FK_Gifts_Users_ReceiverId",
column: x => x.ReceiverId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Gifts_Users_SenderId",
column: x => x.SenderId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ClubMembers",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ClubId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<int>(type: "INTEGER", nullable: false),
Role = table.Column<string>(type: "TEXT", nullable: false),
ContributedPoints = table.Column<int>(type: "INTEGER", nullable: false),
JoinedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ClubMembers", x => x.Id);
table.ForeignKey(
name: "FK_ClubMembers_Clubs_ClubId",
column: x => x.ClubId,
principalTable: "Clubs",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_ClubMembers_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 3, 0, 47, 32, 189, DateTimeKind.Utc).AddTicks(8910), new DateTime(2026, 2, 24, 0, 47, 32, 189, DateTimeKind.Utc).AddTicks(8907) });
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 2,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 3, 0, 47, 32, 189, DateTimeKind.Utc).AddTicks(8918), new DateTime(2026, 2, 24, 0, 47, 32, 189, DateTimeKind.Utc).AddTicks(8918) });
migrationBuilder.CreateIndex(
name: "IX_ClubMembers_ClubId",
table: "ClubMembers",
column: "ClubId");
migrationBuilder.CreateIndex(
name: "IX_ClubMembers_UserId",
table: "ClubMembers",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Clubs_OwnerId",
table: "Clubs",
column: "OwnerId");
migrationBuilder.CreateIndex(
name: "IX_FriendInvitations_ReceiverId",
table: "FriendInvitations",
column: "ReceiverId");
migrationBuilder.CreateIndex(
name: "IX_FriendInvitations_SenderId",
table: "FriendInvitations",
column: "SenderId");
migrationBuilder.CreateIndex(
name: "IX_Friends_User1Id",
table: "Friends",
column: "User1Id");
migrationBuilder.CreateIndex(
name: "IX_Friends_User2Id",
table: "Friends",
column: "User2Id");
migrationBuilder.CreateIndex(
name: "IX_Gifts_ReceiverId",
table: "Gifts",
column: "ReceiverId");
migrationBuilder.CreateIndex(
name: "IX_Gifts_SenderId",
table: "Gifts",
column: "SenderId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ClubMembers");
migrationBuilder.DropTable(
name: "FriendInvitations");
migrationBuilder.DropTable(
name: "Friends");
migrationBuilder.DropTable(
name: "Gifts");
migrationBuilder.DropTable(
name: "Clubs");
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 3, 0, 7, 51, 537, DateTimeKind.Utc).AddTicks(6445), new DateTime(2026, 2, 24, 0, 7, 51, 537, DateTimeKind.Utc).AddTicks(6442) });
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 2,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 3, 0, 7, 51, 537, DateTimeKind.Utc).AddTicks(6454), new DateTime(2026, 2, 24, 0, 7, 51, 537, DateTimeKind.Utc).AddTicks(6453) });
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,241 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RR3CommunityServer.Migrations
{
/// <inheritdoc />
public partial class AddMultiplayerSystem : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "CompetitiveRatings",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<int>(type: "INTEGER", nullable: false),
Rating = table.Column<int>(type: "INTEGER", nullable: false),
Wins = table.Column<int>(type: "INTEGER", nullable: false),
Losses = table.Column<int>(type: "INTEGER", nullable: false),
Draws = table.Column<int>(type: "INTEGER", nullable: false),
Division = table.Column<string>(type: "TEXT", nullable: false),
DivisionRank = table.Column<int>(type: "INTEGER", nullable: false),
LastMatchAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_CompetitiveRatings", x => x.Id);
table.ForeignKey(
name: "FK_CompetitiveRatings_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "GhostData",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<int>(type: "INTEGER", nullable: false),
Track = table.Column<string>(type: "TEXT", nullable: false),
CarId = table.Column<string>(type: "TEXT", nullable: false),
RaceTime = table.Column<double>(type: "REAL", nullable: false),
TelemetryData = table.Column<string>(type: "TEXT", nullable: false),
UploadedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
Downloads = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_GhostData", x => x.Id);
table.ForeignKey(
name: "FK_GhostData_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RaceSessions",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SessionCode = table.Column<string>(type: "TEXT", nullable: false),
Track = table.Column<string>(type: "TEXT", nullable: false),
CarClass = table.Column<string>(type: "TEXT", nullable: false),
HostUserId = table.Column<int>(type: "INTEGER", nullable: false),
MaxPlayers = table.Column<int>(type: "INTEGER", nullable: false),
Status = table.Column<string>(type: "TEXT", nullable: false),
IsPrivate = table.Column<bool>(type: "INTEGER", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
StartedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
FinishedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
HostId = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_RaceSessions", x => x.Id);
table.ForeignKey(
name: "FK_RaceSessions_Users_HostId",
column: x => x.HostId,
principalTable: "Users",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "MatchmakingQueues",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<int>(type: "INTEGER", nullable: false),
CarClass = table.Column<string>(type: "TEXT", nullable: false),
Track = table.Column<string>(type: "TEXT", nullable: false),
GameMode = table.Column<string>(type: "TEXT", nullable: false),
Status = table.Column<string>(type: "TEXT", nullable: false),
QueuedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
MatchedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
SessionId = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_MatchmakingQueues", x => x.Id);
table.ForeignKey(
name: "FK_MatchmakingQueues_RaceSessions_SessionId",
column: x => x.SessionId,
principalTable: "RaceSessions",
principalColumn: "Id");
table.ForeignKey(
name: "FK_MatchmakingQueues_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RaceParticipants",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SessionId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<int>(type: "INTEGER", nullable: false),
CarId = table.Column<string>(type: "TEXT", nullable: false),
IsReady = table.Column<bool>(type: "INTEGER", nullable: false),
FinishPosition = table.Column<int>(type: "INTEGER", nullable: true),
RaceTime = table.Column<double>(type: "REAL", nullable: true),
RewardGold = table.Column<int>(type: "INTEGER", nullable: true),
RewardCash = table.Column<int>(type: "INTEGER", nullable: true),
RewardXP = table.Column<int>(type: "INTEGER", nullable: true),
JoinedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RaceParticipants", x => x.Id);
table.ForeignKey(
name: "FK_RaceParticipants_RaceSessions_SessionId",
column: x => x.SessionId,
principalTable: "RaceSessions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_RaceParticipants_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 3, 0, 53, 48, 427, DateTimeKind.Utc).AddTicks(9290), new DateTime(2026, 2, 24, 0, 53, 48, 427, DateTimeKind.Utc).AddTicks(9287) });
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 2,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 3, 0, 53, 48, 427, DateTimeKind.Utc).AddTicks(9297), new DateTime(2026, 2, 24, 0, 53, 48, 427, DateTimeKind.Utc).AddTicks(9296) });
migrationBuilder.CreateIndex(
name: "IX_CompetitiveRatings_UserId",
table: "CompetitiveRatings",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_GhostData_UserId",
table: "GhostData",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_MatchmakingQueues_SessionId",
table: "MatchmakingQueues",
column: "SessionId");
migrationBuilder.CreateIndex(
name: "IX_MatchmakingQueues_UserId",
table: "MatchmakingQueues",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_RaceParticipants_SessionId",
table: "RaceParticipants",
column: "SessionId");
migrationBuilder.CreateIndex(
name: "IX_RaceParticipants_UserId",
table: "RaceParticipants",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_RaceSessions_HostId",
table: "RaceSessions",
column: "HostId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "CompetitiveRatings");
migrationBuilder.DropTable(
name: "GhostData");
migrationBuilder.DropTable(
name: "MatchmakingQueues");
migrationBuilder.DropTable(
name: "RaceParticipants");
migrationBuilder.DropTable(
name: "RaceSessions");
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 3, 0, 47, 32, 189, DateTimeKind.Utc).AddTicks(8910), new DateTime(2026, 2, 24, 0, 47, 32, 189, DateTimeKind.Utc).AddTicks(8907) });
migrationBuilder.UpdateData(
table: "TimeTrials",
keyColumn: "Id",
keyValue: 2,
columns: new[] { "EndDate", "StartDate" },
values: new object[] { new DateTime(2026, 3, 3, 0, 47, 32, 189, DateTimeKind.Utc).AddTicks(8918), new DateTime(2026, 2, 24, 0, 47, 32, 189, DateTimeKind.Utc).AddTicks(8918) });
}
}
}

View File

@@ -351,6 +351,118 @@ namespace RR3CommunityServer.Migrations
});
});
modelBuilder.Entity("RR3CommunityServer.Data.Club", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsPublic")
.HasColumnType("INTEGER");
b.Property<bool>("IsRecruiting")
.HasColumnType("INTEGER");
b.Property<int>("MaxMembers")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("OwnerId")
.HasColumnType("INTEGER");
b.Property<string>("Tag")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("TotalPoints")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Clubs");
});
modelBuilder.Entity("RR3CommunityServer.Data.ClubMember", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClubId")
.HasColumnType("INTEGER");
b.Property<int>("ContributedPoints")
.HasColumnType("INTEGER");
b.Property<DateTime>("JoinedAt")
.HasColumnType("TEXT");
b.Property<string>("Role")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClubId");
b.HasIndex("UserId");
b.ToTable("ClubMembers");
});
modelBuilder.Entity("RR3CommunityServer.Data.CompetitiveRating", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Division")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("DivisionRank")
.HasColumnType("INTEGER");
b.Property<int>("Draws")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastMatchAt")
.HasColumnType("TEXT");
b.Property<int>("Losses")
.HasColumnType("INTEGER");
b.Property<int>("Rating")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.Property<int>("Wins")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("CompetitiveRatings");
});
modelBuilder.Entity("RR3CommunityServer.Data.DailyReward", b =>
{
b.Property<int>("Id")
@@ -535,6 +647,64 @@ namespace RR3CommunityServer.Migrations
b.ToTable("EventCompletions");
});
modelBuilder.Entity("RR3CommunityServer.Data.Friend", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<int>("User1Id")
.HasColumnType("INTEGER");
b.Property<int>("User2Id")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("User1Id");
b.HasIndex("User2Id");
b.ToTable("Friends");
});
modelBuilder.Entity("RR3CommunityServer.Data.FriendInvitation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<int>("ReceiverId")
.HasColumnType("INTEGER");
b.Property<DateTime?>("RespondedAt")
.HasColumnType("TEXT");
b.Property<int>("SenderId")
.HasColumnType("INTEGER");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ReceiverId");
b.HasIndex("SenderId");
b.ToTable("FriendInvitations");
});
modelBuilder.Entity("RR3CommunityServer.Data.GameAsset", b =>
{
b.Property<int>("Id")
@@ -623,6 +793,86 @@ namespace RR3CommunityServer.Migrations
b.ToTable("GameAssets");
});
modelBuilder.Entity("RR3CommunityServer.Data.GhostData", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CarId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Downloads")
.HasColumnType("INTEGER");
b.Property<double>("RaceTime")
.HasColumnType("REAL");
b.Property<string>("TelemetryData")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Track")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UploadedAt")
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("GhostData");
});
modelBuilder.Entity("RR3CommunityServer.Data.Gift", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("Amount")
.HasColumnType("INTEGER");
b.Property<bool>("Claimed")
.HasColumnType("INTEGER");
b.Property<DateTime?>("ClaimedAt")
.HasColumnType("TEXT");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<string>("GiftType")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Message")
.HasColumnType("TEXT");
b.Property<int>("ReceiverId")
.HasColumnType("INTEGER");
b.Property<int>("SenderId")
.HasColumnType("INTEGER");
b.Property<DateTime>("SentAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ReceiverId");
b.HasIndex("SenderId");
b.ToTable("Gifts");
});
modelBuilder.Entity("RR3CommunityServer.Data.LeaderboardEntry", b =>
{
b.Property<int>("Id")
@@ -662,6 +912,49 @@ namespace RR3CommunityServer.Migrations
b.ToTable("LeaderboardEntries");
});
modelBuilder.Entity("RR3CommunityServer.Data.MatchmakingQueue", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CarClass")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("GameMode")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime?>("MatchedAt")
.HasColumnType("TEXT");
b.Property<DateTime>("QueuedAt")
.HasColumnType("TEXT");
b.Property<int?>("SessionId")
.HasColumnType("INTEGER");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Track")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("SessionId");
b.HasIndex("UserId");
b.ToTable("MatchmakingQueues");
});
modelBuilder.Entity("RR3CommunityServer.Data.ModPack", b =>
{
b.Property<int>("Id")
@@ -908,6 +1201,102 @@ namespace RR3CommunityServer.Migrations
b.ToTable("Purchases");
});
modelBuilder.Entity("RR3CommunityServer.Data.RaceParticipant", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CarId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int?>("FinishPosition")
.HasColumnType("INTEGER");
b.Property<bool>("IsReady")
.HasColumnType("INTEGER");
b.Property<DateTime>("JoinedAt")
.HasColumnType("TEXT");
b.Property<double?>("RaceTime")
.HasColumnType("REAL");
b.Property<int?>("RewardCash")
.HasColumnType("INTEGER");
b.Property<int?>("RewardGold")
.HasColumnType("INTEGER");
b.Property<int?>("RewardXP")
.HasColumnType("INTEGER");
b.Property<int>("SessionId")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("SessionId");
b.HasIndex("UserId");
b.ToTable("RaceParticipants");
});
modelBuilder.Entity("RR3CommunityServer.Data.RaceSession", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CarClass")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<DateTime?>("FinishedAt")
.HasColumnType("TEXT");
b.Property<int?>("HostId")
.HasColumnType("INTEGER");
b.Property<int>("HostUserId")
.HasColumnType("INTEGER");
b.Property<bool>("IsPrivate")
.HasColumnType("INTEGER");
b.Property<int>("MaxPlayers")
.HasColumnType("INTEGER");
b.Property<string>("SessionCode")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime?>("StartedAt")
.HasColumnType("TEXT");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Track")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("HostId");
b.ToTable("RaceSessions");
});
modelBuilder.Entity("RR3CommunityServer.Data.Session", b =>
{
b.Property<int>("Id")
@@ -986,10 +1375,10 @@ namespace RR3CommunityServer.Migrations
Active = true,
CarName = "Any Car",
CashReward = 10000,
EndDate = new DateTime(2026, 3, 3, 0, 7, 51, 537, DateTimeKind.Utc).AddTicks(6445),
EndDate = new DateTime(2026, 3, 3, 0, 53, 48, 427, DateTimeKind.Utc).AddTicks(9290),
GoldReward = 50,
Name = "Daily Sprint Challenge",
StartDate = new DateTime(2026, 2, 24, 0, 7, 51, 537, DateTimeKind.Utc).AddTicks(6442),
StartDate = new DateTime(2026, 2, 24, 0, 53, 48, 427, DateTimeKind.Utc).AddTicks(9287),
TargetTime = 90.5,
TrackName = "Silverstone National"
},
@@ -999,10 +1388,10 @@ namespace RR3CommunityServer.Migrations
Active = true,
CarName = "Any Car",
CashReward = 25000,
EndDate = new DateTime(2026, 3, 3, 0, 7, 51, 537, DateTimeKind.Utc).AddTicks(6454),
EndDate = new DateTime(2026, 3, 3, 0, 53, 48, 427, DateTimeKind.Utc).AddTicks(9297),
GoldReward = 100,
Name = "Speed Demon Trial",
StartDate = new DateTime(2026, 2, 24, 0, 7, 51, 537, DateTimeKind.Utc).AddTicks(6453),
StartDate = new DateTime(2026, 2, 24, 0, 53, 48, 427, DateTimeKind.Utc).AddTicks(9296),
TargetTime = 120.0,
TrackName = "Dubai Autodrome"
});
@@ -1196,6 +1585,47 @@ namespace RR3CommunityServer.Migrations
.IsRequired();
});
modelBuilder.Entity("RR3CommunityServer.Data.Club", b =>
{
b.HasOne("RR3CommunityServer.Data.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
modelBuilder.Entity("RR3CommunityServer.Data.ClubMember", b =>
{
b.HasOne("RR3CommunityServer.Data.Club", "Club")
.WithMany("Members")
.HasForeignKey("ClubId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("RR3CommunityServer.Data.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Club");
b.Navigation("User");
});
modelBuilder.Entity("RR3CommunityServer.Data.CompetitiveRating", b =>
{
b.HasOne("RR3CommunityServer.Data.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("RR3CommunityServer.Data.EventAttempt", b =>
{
b.HasOne("RR3CommunityServer.Data.Event", "Event")
@@ -1234,6 +1664,91 @@ namespace RR3CommunityServer.Migrations
b.Navigation("User");
});
modelBuilder.Entity("RR3CommunityServer.Data.Friend", b =>
{
b.HasOne("RR3CommunityServer.Data.User", "User1")
.WithMany()
.HasForeignKey("User1Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("RR3CommunityServer.Data.User", "User2")
.WithMany()
.HasForeignKey("User2Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User1");
b.Navigation("User2");
});
modelBuilder.Entity("RR3CommunityServer.Data.FriendInvitation", b =>
{
b.HasOne("RR3CommunityServer.Data.User", "Receiver")
.WithMany()
.HasForeignKey("ReceiverId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("RR3CommunityServer.Data.User", "Sender")
.WithMany()
.HasForeignKey("SenderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Receiver");
b.Navigation("Sender");
});
modelBuilder.Entity("RR3CommunityServer.Data.GhostData", b =>
{
b.HasOne("RR3CommunityServer.Data.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("RR3CommunityServer.Data.Gift", b =>
{
b.HasOne("RR3CommunityServer.Data.User", "Receiver")
.WithMany()
.HasForeignKey("ReceiverId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("RR3CommunityServer.Data.User", "Sender")
.WithMany()
.HasForeignKey("SenderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Receiver");
b.Navigation("Sender");
});
modelBuilder.Entity("RR3CommunityServer.Data.MatchmakingQueue", b =>
{
b.HasOne("RR3CommunityServer.Data.RaceSession", "Session")
.WithMany()
.HasForeignKey("SessionId");
b.HasOne("RR3CommunityServer.Data.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Session");
b.Navigation("User");
});
modelBuilder.Entity("RR3CommunityServer.Data.Notification", b =>
{
b.HasOne("RR3CommunityServer.Data.User", "User")
@@ -1254,6 +1769,34 @@ namespace RR3CommunityServer.Migrations
.IsRequired();
});
modelBuilder.Entity("RR3CommunityServer.Data.RaceParticipant", b =>
{
b.HasOne("RR3CommunityServer.Data.RaceSession", "Session")
.WithMany("Participants")
.HasForeignKey("SessionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("RR3CommunityServer.Data.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Session");
b.Navigation("User");
});
modelBuilder.Entity("RR3CommunityServer.Data.RaceSession", b =>
{
b.HasOne("RR3CommunityServer.Data.User", "Host")
.WithMany()
.HasForeignKey("HostId");
b.Navigation("Host");
});
modelBuilder.Entity("RR3CommunityServer.Data.TimeTrialResult", b =>
{
b.HasOne("RR3CommunityServer.Data.TimeTrial", "TimeTrial")
@@ -1293,6 +1836,16 @@ namespace RR3CommunityServer.Migrations
b.Navigation("Account");
});
modelBuilder.Entity("RR3CommunityServer.Data.Club", b =>
{
b.Navigation("Members");
});
modelBuilder.Entity("RR3CommunityServer.Data.RaceSession", b =>
{
b.Navigation("Participants");
});
modelBuilder.Entity("RR3CommunityServer.Data.User", b =>
{
b.Navigation("CareerProgress");