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:
853
RR3CommunityServer/Controllers/FriendsController.cs
Normal file
853
RR3CommunityServer/Controllers/FriendsController.cs
Normal file
@@ -0,0 +1,853 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RR3CommunityServer.Data;
|
||||
using RR3CommunityServer.Models;
|
||||
|
||||
namespace RR3CommunityServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("synergy/friends")]
|
||||
public class FriendsController : ControllerBase
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
private readonly ILogger<FriendsController> _logger;
|
||||
|
||||
public FriendsController(RR3DbContext context, ILogger<FriendsController> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// ===== FRIEND MANAGEMENT (4 endpoints) =====
|
||||
|
||||
// GET /synergy/friends/list - Get player's friend list
|
||||
[HttpGet("list")]
|
||||
public async Task<ActionResult<FriendsListResponse>> GetFriendsList([FromQuery] string synergyId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == synergyId);
|
||||
if (user == null)
|
||||
{
|
||||
return Ok(new FriendsListResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "User not found"
|
||||
});
|
||||
}
|
||||
|
||||
// Get friends where user is either User1 or User2
|
||||
var friends = await _context.Friends
|
||||
.Where(f => f.User1Id == user.Id || f.User2Id == user.Id)
|
||||
.Include(f => f.User1)
|
||||
.Include(f => f.User2)
|
||||
.ToListAsync();
|
||||
|
||||
var friendDtos = friends.Select(f =>
|
||||
{
|
||||
var friend = f.User1Id == user.Id ? f.User2 : f.User1;
|
||||
return new FriendDto
|
||||
{
|
||||
UserId = friend!.Id,
|
||||
Nickname = friend.Nickname ?? "Player",
|
||||
SynergyId = friend.SynergyId,
|
||||
Level = friend.Level ?? 1,
|
||||
LastOnline = friend.CreatedAt,
|
||||
FriendsSince = f.CreatedAt
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
return Ok(new FriendsListResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
Friends = friendDtos,
|
||||
TotalCount = friendDtos.Count
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting friends list for {SynergyId}", synergyId);
|
||||
return Ok(new FriendsListResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error loading friends"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// POST /synergy/friends/add - Send friend request
|
||||
[HttpPost("add")]
|
||||
public async Task<ActionResult<SimpleResponse>> SendFriendRequest(
|
||||
[FromQuery] string synergyId,
|
||||
[FromQuery] string? targetSynergyId = null,
|
||||
[FromQuery] string? targetUsername = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == synergyId);
|
||||
if (user == null)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "User not found"
|
||||
});
|
||||
}
|
||||
|
||||
// Find target user by SynergyId or Username
|
||||
User? targetUser = null;
|
||||
if (!string.IsNullOrEmpty(targetSynergyId))
|
||||
{
|
||||
targetUser = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == targetSynergyId);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(targetUsername))
|
||||
{
|
||||
targetUser = await _context.Users.FirstOrDefaultAsync(u => u.Nickname == targetUsername);
|
||||
}
|
||||
|
||||
if (targetUser == null)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -2,
|
||||
Message = "Target user not found"
|
||||
});
|
||||
}
|
||||
|
||||
if (user.Id == targetUser.Id)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -3,
|
||||
Message = "Cannot add yourself as friend"
|
||||
});
|
||||
}
|
||||
|
||||
// Check if already friends
|
||||
var existingFriendship = await _context.Friends
|
||||
.AnyAsync(f => (f.User1Id == user.Id && f.User2Id == targetUser.Id) ||
|
||||
(f.User1Id == targetUser.Id && f.User2Id == user.Id));
|
||||
|
||||
if (existingFriendship)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -4,
|
||||
Message = "Already friends"
|
||||
});
|
||||
}
|
||||
|
||||
// Check for existing pending invitation
|
||||
var existingInvite = await _context.FriendInvitations
|
||||
.FirstOrDefaultAsync(i => i.SenderId == user.Id && i.ReceiverId == targetUser.Id && i.Status == "pending");
|
||||
|
||||
if (existingInvite != null)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -5,
|
||||
Message = "Friend request already sent"
|
||||
});
|
||||
}
|
||||
|
||||
// Create friend invitation
|
||||
var invitation = new FriendInvitation
|
||||
{
|
||||
SenderId = user.Id,
|
||||
ReceiverId = targetUser.Id,
|
||||
Status = "pending",
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(7)
|
||||
};
|
||||
|
||||
_context.FriendInvitations.Add(invitation);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Friend request sent: {Sender} -> {Receiver}", user.SynergyId, targetUser.SynergyId);
|
||||
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
Message = "Friend request sent"
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error sending friend request");
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error sending request"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// POST /synergy/friends/accept - Accept friend request
|
||||
[HttpPost("accept")]
|
||||
public async Task<ActionResult<SimpleResponse>> AcceptFriendRequest(
|
||||
[FromQuery] string synergyId,
|
||||
[FromQuery] int invitationId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == synergyId);
|
||||
if (user == null)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "User not found"
|
||||
});
|
||||
}
|
||||
|
||||
var invitation = await _context.FriendInvitations
|
||||
.FirstOrDefaultAsync(i => i.Id == invitationId && i.ReceiverId == user.Id && i.Status == "pending");
|
||||
|
||||
if (invitation == null)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -2,
|
||||
Message = "Invitation not found"
|
||||
});
|
||||
}
|
||||
|
||||
if (invitation.ExpiresAt < DateTime.UtcNow)
|
||||
{
|
||||
invitation.Status = "expired";
|
||||
await _context.SaveChangesAsync();
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -3,
|
||||
Message = "Invitation expired"
|
||||
});
|
||||
}
|
||||
|
||||
// Create friendship
|
||||
var friendship = new Friend
|
||||
{
|
||||
User1Id = invitation.SenderId,
|
||||
User2Id = invitation.ReceiverId,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.Friends.Add(friendship);
|
||||
|
||||
// Update invitation status
|
||||
invitation.Status = "accepted";
|
||||
invitation.RespondedAt = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Friend request accepted: {Invitation}", invitationId);
|
||||
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
Message = "Friend request accepted"
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error accepting friend request");
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error accepting request"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /synergy/friends/remove - Remove friend
|
||||
[HttpDelete("remove")]
|
||||
public async Task<ActionResult<SimpleResponse>> RemoveFriend(
|
||||
[FromQuery] string synergyId,
|
||||
[FromQuery] string friendSynergyId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == synergyId);
|
||||
var friend = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == friendSynergyId);
|
||||
|
||||
if (user == null || friend == null)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "User not found"
|
||||
});
|
||||
}
|
||||
|
||||
var friendship = await _context.Friends
|
||||
.FirstOrDefaultAsync(f => (f.User1Id == user.Id && f.User2Id == friend.Id) ||
|
||||
(f.User1Id == friend.Id && f.User2Id == user.Id));
|
||||
|
||||
if (friendship == null)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -2,
|
||||
Message = "Not friends"
|
||||
});
|
||||
}
|
||||
|
||||
_context.Friends.Remove(friendship);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Friendship removed: {User1} <-> {User2}", user.SynergyId, friend.SynergyId);
|
||||
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
Message = "Friend removed"
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error removing friend");
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error removing friend"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ===== SEARCH & DISCOVERY (2 endpoints) =====
|
||||
|
||||
// GET /synergy/friends/search - Search for players
|
||||
[HttpGet("search")]
|
||||
public async Task<ActionResult<UserSearchResponse>> SearchUsers(
|
||||
[FromQuery] string query,
|
||||
[FromQuery] string? requestingSynergyId = null,
|
||||
[FromQuery] int limit = 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
User? requestingUser = null;
|
||||
if (!string.IsNullOrEmpty(requestingSynergyId))
|
||||
{
|
||||
requestingUser = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == requestingSynergyId);
|
||||
}
|
||||
|
||||
var users = await _context.Users
|
||||
.Where(u => (u.Nickname != null && u.Nickname.Contains(query)) || u.SynergyId.Contains(query))
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
|
||||
var results = new List<UserSearchResultDto>();
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
bool isFriend = false;
|
||||
bool hasPendingInvite = false;
|
||||
|
||||
if (requestingUser != null)
|
||||
{
|
||||
isFriend = await _context.Friends.AnyAsync(f =>
|
||||
(f.User1Id == requestingUser.Id && f.User2Id == user.Id) ||
|
||||
(f.User1Id == user.Id && f.User2Id == requestingUser.Id));
|
||||
|
||||
hasPendingInvite = await _context.FriendInvitations.AnyAsync(i =>
|
||||
i.SenderId == requestingUser.Id && i.ReceiverId == user.Id && i.Status == "pending");
|
||||
}
|
||||
|
||||
results.Add(new UserSearchResultDto
|
||||
{
|
||||
UserId = user.Id,
|
||||
Nickname = user.Nickname ?? "Player",
|
||||
SynergyId = user.SynergyId,
|
||||
Level = user.Level ?? 1,
|
||||
IsFriend = isFriend,
|
||||
HasPendingInvite = hasPendingInvite
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(new UserSearchResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
Users = results
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error searching users");
|
||||
return Ok(new UserSearchResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error searching users"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// GET /synergy/friends/invitations/pending - Get pending friend invitations
|
||||
[HttpGet("invitations/pending")]
|
||||
public async Task<ActionResult<PendingInvitationsResponse>> GetPendingInvitations([FromQuery] string synergyId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == synergyId);
|
||||
if (user == null)
|
||||
{
|
||||
return Ok(new PendingInvitationsResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "User not found"
|
||||
});
|
||||
}
|
||||
|
||||
var invitations = await _context.FriendInvitations
|
||||
.Where(i => i.ReceiverId == user.Id && i.Status == "pending" && i.ExpiresAt > DateTime.UtcNow)
|
||||
.Include(i => i.Sender)
|
||||
.OrderByDescending(i => i.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
var invitationDtos = invitations.Select(i => new FriendInvitationDto
|
||||
{
|
||||
InvitationId = i.Id,
|
||||
SenderId = i.SenderId,
|
||||
SenderNickname = i.Sender!.Nickname ?? "Player",
|
||||
SenderSynergyId = i.Sender.SynergyId,
|
||||
SenderLevel = i.Sender.Level ?? 1,
|
||||
Status = i.Status,
|
||||
CreatedAt = i.CreatedAt,
|
||||
ExpiresAt = i.ExpiresAt
|
||||
}).ToList();
|
||||
|
||||
return Ok(new PendingInvitationsResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
Invitations = invitationDtos
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting pending invitations");
|
||||
return Ok(new PendingInvitationsResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error loading invitations"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ===== GIFTS (3 endpoints) =====
|
||||
|
||||
// POST /synergy/friends/gift/send - Send gift to friend
|
||||
[HttpPost("gift/send")]
|
||||
public async Task<ActionResult<SimpleResponse>> SendGift([FromBody] SendGiftRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sender = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == request.SynergyId);
|
||||
var receiver = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == request.FriendSynergyId);
|
||||
|
||||
if (sender == null || receiver == null)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "User not found"
|
||||
});
|
||||
}
|
||||
|
||||
// Verify they're friends
|
||||
var areFriends = await _context.Friends.AnyAsync(f =>
|
||||
(f.User1Id == sender.Id && f.User2Id == receiver.Id) ||
|
||||
(f.User1Id == receiver.Id && f.User2Id == sender.Id));
|
||||
|
||||
if (!areFriends)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -2,
|
||||
Message = "Not friends"
|
||||
});
|
||||
}
|
||||
|
||||
// Create gift
|
||||
var gift = new Gift
|
||||
{
|
||||
SenderId = sender.Id,
|
||||
ReceiverId = receiver.Id,
|
||||
GiftType = request.GiftType,
|
||||
Amount = request.Amount,
|
||||
Message = request.Message,
|
||||
SentAt = DateTime.UtcNow,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(7)
|
||||
};
|
||||
|
||||
_context.Gifts.Add(gift);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Gift sent: {Sender} -> {Receiver} ({Type} x{Amount})",
|
||||
sender.SynergyId, receiver.SynergyId, request.GiftType, request.Amount);
|
||||
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
Message = "Gift sent"
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error sending gift");
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error sending gift"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// GET /synergy/friends/gifts/pending - Get pending gifts
|
||||
[HttpGet("gifts/pending")]
|
||||
public async Task<ActionResult<PendingGiftsResponse>> GetPendingGifts([FromQuery] string synergyId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == synergyId);
|
||||
if (user == null)
|
||||
{
|
||||
return Ok(new PendingGiftsResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "User not found"
|
||||
});
|
||||
}
|
||||
|
||||
var gifts = await _context.Gifts
|
||||
.Where(g => g.ReceiverId == user.Id && !g.Claimed && g.ExpiresAt > DateTime.UtcNow)
|
||||
.Include(g => g.Sender)
|
||||
.OrderByDescending(g => g.SentAt)
|
||||
.ToListAsync();
|
||||
|
||||
var giftDtos = gifts.Select(g => new GiftDto
|
||||
{
|
||||
GiftId = g.Id,
|
||||
SenderId = g.SenderId,
|
||||
SenderNickname = g.Sender!.Nickname ?? "Player",
|
||||
GiftType = g.GiftType,
|
||||
Amount = g.Amount,
|
||||
Message = g.Message,
|
||||
SentAt = g.SentAt,
|
||||
ExpiresAt = g.ExpiresAt
|
||||
}).ToList();
|
||||
|
||||
return Ok(new PendingGiftsResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
Gifts = giftDtos
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting pending gifts");
|
||||
return Ok(new PendingGiftsResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error loading gifts"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// POST /synergy/friends/gifts/claim - Claim a gift
|
||||
[HttpPost("gifts/claim")]
|
||||
public async Task<ActionResult<ClaimGiftResponse>> ClaimGift(
|
||||
[FromQuery] string synergyId,
|
||||
[FromQuery] int giftId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == synergyId);
|
||||
if (user == null)
|
||||
{
|
||||
return Ok(new ClaimGiftResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "User not found"
|
||||
});
|
||||
}
|
||||
|
||||
var gift = await _context.Gifts
|
||||
.FirstOrDefaultAsync(g => g.Id == giftId && g.ReceiverId == user.Id && !g.Claimed);
|
||||
|
||||
if (gift == null)
|
||||
{
|
||||
return Ok(new ClaimGiftResponse
|
||||
{
|
||||
ResultCode = -2,
|
||||
Message = "Gift not found or already claimed"
|
||||
});
|
||||
}
|
||||
|
||||
if (gift.ExpiresAt < DateTime.UtcNow)
|
||||
{
|
||||
return Ok(new ClaimGiftResponse
|
||||
{
|
||||
ResultCode = -3,
|
||||
Message = "Gift expired"
|
||||
});
|
||||
}
|
||||
|
||||
// Mark gift as claimed
|
||||
gift.Claimed = true;
|
||||
gift.ClaimedAt = DateTime.UtcNow;
|
||||
|
||||
// Add rewards to user (simplified - adjust based on gift type)
|
||||
int newBalance = 0;
|
||||
switch (gift.GiftType.ToLower())
|
||||
{
|
||||
case "gold":
|
||||
user.Gold += gift.Amount;
|
||||
newBalance = user.Gold ?? 0;
|
||||
break;
|
||||
case "cash":
|
||||
user.Cash += gift.Amount;
|
||||
newBalance = user.Cash ?? 0;
|
||||
break;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Gift claimed: {User} claimed gift {GiftId} ({Type} x{Amount})",
|
||||
user.SynergyId, giftId, gift.GiftType, gift.Amount);
|
||||
|
||||
return Ok(new ClaimGiftResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
GiftType = gift.GiftType,
|
||||
Amount = gift.Amount,
|
||||
NewBalance = newBalance
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error claiming gift");
|
||||
return Ok(new ClaimGiftResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error claiming gift"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ===== CLUBS/TEAMS (3 endpoints) =====
|
||||
|
||||
// GET /synergy/clubs/list - Get available clubs
|
||||
[HttpGet("/synergy/clubs/list")]
|
||||
public async Task<ActionResult<ClubsListResponse>> GetClubsList(
|
||||
[FromQuery] bool publicOnly = true,
|
||||
[FromQuery] bool recruitingOnly = false,
|
||||
[FromQuery] int limit = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = _context.Clubs.AsQueryable();
|
||||
|
||||
if (publicOnly)
|
||||
query = query.Where(c => c.IsPublic);
|
||||
|
||||
if (recruitingOnly)
|
||||
query = query.Where(c => c.IsRecruiting);
|
||||
|
||||
var clubs = await query
|
||||
.OrderByDescending(c => c.TotalPoints)
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
|
||||
var clubDtos = new List<ClubDto>();
|
||||
foreach (var club in clubs)
|
||||
{
|
||||
var memberCount = await _context.ClubMembers.CountAsync(m => m.ClubId == club.Id);
|
||||
|
||||
clubDtos.Add(new ClubDto
|
||||
{
|
||||
ClubId = club.Id,
|
||||
Name = club.Name,
|
||||
Description = club.Description,
|
||||
Tag = club.Tag,
|
||||
MemberCount = memberCount,
|
||||
MaxMembers = club.MaxMembers,
|
||||
IsPublic = club.IsPublic,
|
||||
IsRecruiting = club.IsRecruiting,
|
||||
TotalPoints = club.TotalPoints,
|
||||
CreatedAt = club.CreatedAt
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(new ClubsListResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
Clubs = clubDtos
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting clubs list");
|
||||
return Ok(new ClubsListResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error loading clubs"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// POST /synergy/clubs/join - Join a club
|
||||
[HttpPost("/synergy/clubs/join")]
|
||||
public async Task<ActionResult<SimpleResponse>> JoinClub(
|
||||
[FromQuery] string synergyId,
|
||||
[FromQuery] int clubId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _context.Users.FirstOrDefaultAsync(u => u.SynergyId == synergyId);
|
||||
if (user == null)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "User not found"
|
||||
});
|
||||
}
|
||||
|
||||
var club = await _context.Clubs.FirstOrDefaultAsync(c => c.Id == clubId);
|
||||
if (club == null)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -2,
|
||||
Message = "Club not found"
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is already in a club
|
||||
var existingMembership = await _context.ClubMembers
|
||||
.AnyAsync(m => m.UserId == user.Id);
|
||||
|
||||
if (existingMembership)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -3,
|
||||
Message = "Already in a club"
|
||||
});
|
||||
}
|
||||
|
||||
// Check if club is full
|
||||
var memberCount = await _context.ClubMembers.CountAsync(m => m.ClubId == clubId);
|
||||
if (memberCount >= club.MaxMembers)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -4,
|
||||
Message = "Club is full"
|
||||
});
|
||||
}
|
||||
|
||||
// Check if club is recruiting
|
||||
if (!club.IsRecruiting && club.OwnerId != user.Id)
|
||||
{
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -5,
|
||||
Message = "Club is not recruiting"
|
||||
});
|
||||
}
|
||||
|
||||
// Add member
|
||||
var member = new ClubMember
|
||||
{
|
||||
ClubId = clubId,
|
||||
UserId = user.Id,
|
||||
Role = "member",
|
||||
JoinedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.ClubMembers.Add(member);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("User {User} joined club {Club}", user.SynergyId, club.Name);
|
||||
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
Message = "Joined club successfully"
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error joining club");
|
||||
return Ok(new SimpleResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error joining club"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// GET /synergy/clubs/{clubId}/members - Get club members
|
||||
[HttpGet("/synergy/clubs/{clubId}/members")]
|
||||
public async Task<ActionResult<ClubMembersResponse>> GetClubMembers(int clubId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var club = await _context.Clubs.FirstOrDefaultAsync(c => c.Id == clubId);
|
||||
if (club == null)
|
||||
{
|
||||
return Ok(new ClubMembersResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Club not found"
|
||||
});
|
||||
}
|
||||
|
||||
var members = await _context.ClubMembers
|
||||
.Where(m => m.ClubId == clubId)
|
||||
.Include(m => m.User)
|
||||
.OrderByDescending(m => m.ContributedPoints)
|
||||
.ToListAsync();
|
||||
|
||||
var memberDtos = members.Select(m => new ClubMemberDto
|
||||
{
|
||||
UserId = m.UserId,
|
||||
Nickname = m.User!.Nickname ?? "Player",
|
||||
SynergyId = m.User.SynergyId,
|
||||
Level = m.User.Level ?? 1,
|
||||
Role = m.Role,
|
||||
ContributedPoints = m.ContributedPoints,
|
||||
JoinedAt = m.JoinedAt
|
||||
}).ToList();
|
||||
|
||||
var memberCount = members.Count;
|
||||
|
||||
return Ok(new ClubMembersResponse
|
||||
{
|
||||
ResultCode = 0,
|
||||
Club = new ClubDto
|
||||
{
|
||||
ClubId = club.Id,
|
||||
Name = club.Name,
|
||||
Description = club.Description,
|
||||
Tag = club.Tag,
|
||||
MemberCount = memberCount,
|
||||
MaxMembers = club.MaxMembers,
|
||||
IsPublic = club.IsPublic,
|
||||
IsRecruiting = club.IsRecruiting,
|
||||
TotalPoints = club.TotalPoints,
|
||||
CreatedAt = club.CreatedAt
|
||||
},
|
||||
Members = memberDtos
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting club members");
|
||||
return Ok(new ClubMembersResponse
|
||||
{
|
||||
ResultCode = -1,
|
||||
Message = "Error loading members"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
1012
RR3CommunityServer/Controllers/MultiplayerController.cs
Normal file
1012
RR3CommunityServer/Controllers/MultiplayerController.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,16 @@ public class RR3DbContext : DbContext
|
||||
public DbSet<EventCompletion> EventCompletions { get; set; }
|
||||
public DbSet<EventAttempt> EventAttempts { get; set; }
|
||||
public DbSet<Notification> Notifications { get; set; }
|
||||
public DbSet<Friend> Friends { get; set; }
|
||||
public DbSet<FriendInvitation> FriendInvitations { get; set; }
|
||||
public DbSet<Gift> Gifts { get; set; }
|
||||
public DbSet<Club> Clubs { get; set; }
|
||||
public DbSet<ClubMember> ClubMembers { get; set; }
|
||||
public DbSet<MatchmakingQueue> MatchmakingQueues { get; set; }
|
||||
public DbSet<RaceSession> RaceSessions { get; set; }
|
||||
public DbSet<RaceParticipant> RaceParticipants { get; set; }
|
||||
public DbSet<GhostData> GhostData { get; set; }
|
||||
public DbSet<CompetitiveRating> CompetitiveRatings { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -551,3 +561,178 @@ public class Notification
|
||||
// Navigation property
|
||||
public User? User { get; set; }
|
||||
}
|
||||
|
||||
// Friend relationships
|
||||
public class Friend
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int User1Id { get; set; }
|
||||
public int User2Id { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation properties
|
||||
public User? User1 { get; set; }
|
||||
public User? User2 { get; set; }
|
||||
}
|
||||
|
||||
// Friend invitations (pending requests)
|
||||
public class FriendInvitation
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int SenderId { get; set; }
|
||||
public int ReceiverId { get; set; }
|
||||
public string Status { get; set; } = "pending"; // "pending", "accepted", "declined", "expired"
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? RespondedAt { get; set; }
|
||||
public DateTime ExpiresAt { get; set; } = DateTime.UtcNow.AddDays(7);
|
||||
|
||||
// Navigation properties
|
||||
public User? Sender { get; set; }
|
||||
public User? Receiver { get; set; }
|
||||
}
|
||||
|
||||
// Gifts between friends
|
||||
public class Gift
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int SenderId { get; set; }
|
||||
public int ReceiverId { get; set; }
|
||||
public string GiftType { get; set; } = string.Empty; // "gold", "cash", "boost"
|
||||
public int Amount { get; set; }
|
||||
public string? Message { get; set; }
|
||||
public bool Claimed { get; set; } = false;
|
||||
public DateTime SentAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? ClaimedAt { get; set; }
|
||||
public DateTime ExpiresAt { get; set; } = DateTime.UtcNow.AddDays(7);
|
||||
|
||||
// Navigation properties
|
||||
public User? Sender { get; set; }
|
||||
public User? Receiver { get; set; }
|
||||
}
|
||||
|
||||
// Clubs/Teams
|
||||
public class Club
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Tag { get; set; } = string.Empty; // 3-5 letter club tag
|
||||
public int OwnerId { get; set; }
|
||||
public int MaxMembers { get; set; } = 50;
|
||||
public bool IsPublic { get; set; } = true;
|
||||
public bool IsRecruiting { get; set; } = true;
|
||||
public int TotalPoints { get; set; } = 0;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation properties
|
||||
public User? Owner { get; set; }
|
||||
public ICollection<ClubMember> Members { get; set; } = new List<ClubMember>();
|
||||
}
|
||||
|
||||
// Club memberships
|
||||
public class ClubMember
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int ClubId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public string Role { get; set; } = "member"; // "owner", "admin", "member"
|
||||
public int ContributedPoints { get; set; } = 0;
|
||||
public DateTime JoinedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation properties
|
||||
public Club? Club { get; set; }
|
||||
public User? User { get; set; }
|
||||
}
|
||||
|
||||
// ===== MULTIPLAYER SYSTEM ENTITIES =====
|
||||
|
||||
// Matchmaking queue entries
|
||||
public class MatchmakingQueue
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public string CarClass { get; set; } = string.Empty;
|
||||
public string Track { get; set; } = string.Empty;
|
||||
public string GameMode { get; set; } = string.Empty; // "ranked", "casual", "private"
|
||||
public string Status { get; set; } = "queued"; // "queued", "matched", "cancelled"
|
||||
public DateTime QueuedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? MatchedAt { get; set; }
|
||||
public int? SessionId { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public User? User { get; set; }
|
||||
public RaceSession? Session { get; set; }
|
||||
}
|
||||
|
||||
// Race sessions (lobbies)
|
||||
public class RaceSession
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string SessionCode { get; set; } = string.Empty; // 6-digit join code
|
||||
public string Track { get; set; } = string.Empty;
|
||||
public string CarClass { get; set; } = string.Empty;
|
||||
public int HostUserId { get; set; }
|
||||
public int MaxPlayers { get; set; } = 8;
|
||||
public string Status { get; set; } = "lobby"; // "lobby", "countdown", "racing", "finished"
|
||||
public bool IsPrivate { get; set; } = false;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? StartedAt { get; set; }
|
||||
public DateTime? FinishedAt { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public User? Host { get; set; }
|
||||
public ICollection<RaceParticipant> Participants { get; set; } = new List<RaceParticipant>();
|
||||
}
|
||||
|
||||
// Race session participants
|
||||
public class RaceParticipant
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int SessionId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public string CarId { get; set; } = string.Empty;
|
||||
public bool IsReady { get; set; } = false;
|
||||
public int? FinishPosition { get; set; }
|
||||
public double? RaceTime { get; set; }
|
||||
public int? RewardGold { get; set; }
|
||||
public int? RewardCash { get; set; }
|
||||
public int? RewardXP { get; set; }
|
||||
public DateTime JoinedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation properties
|
||||
public RaceSession? Session { get; set; }
|
||||
public User? User { get; set; }
|
||||
}
|
||||
|
||||
// Ghost race data
|
||||
public class GhostData
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public string Track { get; set; } = string.Empty;
|
||||
public string CarId { get; set; } = string.Empty;
|
||||
public double RaceTime { get; set; }
|
||||
public string TelemetryData { get; set; } = string.Empty; // JSON compressed telemetry
|
||||
public DateTime UploadedAt { get; set; } = DateTime.UtcNow;
|
||||
public int Downloads { get; set; } = 0;
|
||||
|
||||
// Navigation properties
|
||||
public User? User { get; set; }
|
||||
}
|
||||
|
||||
// Competitive ratings (ranked mode)
|
||||
public class CompetitiveRating
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public int Rating { get; set; } = 1000; // ELO-style rating
|
||||
public int Wins { get; set; } = 0;
|
||||
public int Losses { get; set; } = 0;
|
||||
public int Draws { get; set; } = 0;
|
||||
public string Division { get; set; } = "Bronze"; // Bronze, Silver, Gold, Platinum, Diamond
|
||||
public int DivisionRank { get; set; } = 0;
|
||||
public DateTime LastMatchAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation properties
|
||||
public User? User { get; set; }
|
||||
}
|
||||
|
||||
1580
RR3CommunityServer/Migrations/20260224004732_AddFriendsSocialSystem.Designer.cs
generated
Normal file
1580
RR3CommunityServer/Migrations/20260224004732_AddFriendsSocialSystem.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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) });
|
||||
}
|
||||
}
|
||||
}
|
||||
1866
RR3CommunityServer/Migrations/20260224005348_AddMultiplayerSystem.Designer.cs
generated
Normal file
1866
RR3CommunityServer/Migrations/20260224005348_AddMultiplayerSystem.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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) });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -368,3 +368,327 @@ public class SendNotificationRequest
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public int? ExpiresInHours { get; set; } // null = never expires
|
||||
}
|
||||
|
||||
// ===== FRIENDS/SOCIAL SYSTEM MODELS =====
|
||||
|
||||
// Simple response with just resultCode and message (no data)
|
||||
public class SimpleResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
|
||||
// Friend DTO
|
||||
public class FriendDto
|
||||
{
|
||||
public int UserId { get; set; }
|
||||
public string Nickname { get; set; } = string.Empty;
|
||||
public string SynergyId { get; set; } = string.Empty;
|
||||
public int Level { get; set; }
|
||||
public DateTime? LastOnline { get; set; }
|
||||
public DateTime FriendsSince { get; set; }
|
||||
}
|
||||
|
||||
// Friend list response
|
||||
public class FriendsListResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public List<FriendDto> Friends { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
|
||||
// Friend invitation DTO
|
||||
public class FriendInvitationDto
|
||||
{
|
||||
public int InvitationId { get; set; }
|
||||
public int SenderId { get; set; }
|
||||
public string SenderNickname { get; set; } = string.Empty;
|
||||
public string SenderSynergyId { get; set; } = string.Empty;
|
||||
public int SenderLevel { get; set; }
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
}
|
||||
|
||||
// Pending invitations response
|
||||
public class PendingInvitationsResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public List<FriendInvitationDto> Invitations { get; set; } = new();
|
||||
}
|
||||
|
||||
// User search result DTO
|
||||
public class UserSearchResultDto
|
||||
{
|
||||
public int UserId { get; set; }
|
||||
public string Nickname { get; set; } = string.Empty;
|
||||
public string SynergyId { get; set; } = string.Empty;
|
||||
public int Level { get; set; }
|
||||
public bool IsFriend { get; set; }
|
||||
public bool HasPendingInvite { get; set; }
|
||||
}
|
||||
|
||||
// User search response
|
||||
public class UserSearchResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public List<UserSearchResultDto> Users { get; set; } = new();
|
||||
}
|
||||
|
||||
// Gift DTO
|
||||
public class GiftDto
|
||||
{
|
||||
public int GiftId { get; set; }
|
||||
public int SenderId { get; set; }
|
||||
public string SenderNickname { get; set; } = string.Empty;
|
||||
public string GiftType { get; set; } = string.Empty;
|
||||
public int Amount { get; set; }
|
||||
public string? Message { get; set; }
|
||||
public DateTime SentAt { get; set; }
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
}
|
||||
|
||||
// Pending gifts response
|
||||
public class PendingGiftsResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public List<GiftDto> Gifts { get; set; } = new();
|
||||
}
|
||||
|
||||
// Send gift request
|
||||
public class SendGiftRequest
|
||||
{
|
||||
public string SynergyId { get; set; } = string.Empty;
|
||||
public string FriendSynergyId { get; set; } = string.Empty;
|
||||
public string GiftType { get; set; } = string.Empty;
|
||||
public int Amount { get; set; }
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
|
||||
// Claim gift response
|
||||
public class ClaimGiftResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public string GiftType { get; set; } = string.Empty;
|
||||
public int Amount { get; set; }
|
||||
public int NewBalance { get; set; }
|
||||
}
|
||||
|
||||
// Club DTO
|
||||
public class ClubDto
|
||||
{
|
||||
public int ClubId { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Tag { get; set; } = string.Empty;
|
||||
public int MemberCount { get; set; }
|
||||
public int MaxMembers { get; set; }
|
||||
public bool IsPublic { get; set; }
|
||||
public bool IsRecruiting { get; set; }
|
||||
public int TotalPoints { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
// Club list response
|
||||
public class ClubsListResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public List<ClubDto> Clubs { get; set; } = new();
|
||||
}
|
||||
|
||||
// Club member DTO
|
||||
public class ClubMemberDto
|
||||
{
|
||||
public int UserId { get; set; }
|
||||
public string Nickname { get; set; } = string.Empty;
|
||||
public string SynergyId { get; set; } = string.Empty;
|
||||
public int Level { get; set; }
|
||||
public string Role { get; set; } = string.Empty;
|
||||
public int ContributedPoints { get; set; }
|
||||
public DateTime JoinedAt { get; set; }
|
||||
}
|
||||
|
||||
// Club members response
|
||||
public class ClubMembersResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public ClubDto Club { get; set; } = new();
|
||||
public List<ClubMemberDto> Members { get; set; } = new();
|
||||
}
|
||||
|
||||
// Create club request
|
||||
public class CreateClubRequest
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Tag { get; set; } = string.Empty;
|
||||
public bool IsPublic { get; set; } = true;
|
||||
}
|
||||
|
||||
// ===== MULTIPLAYER SYSTEM MODELS =====
|
||||
|
||||
// Matchmaking queue request
|
||||
public class JoinMatchmakingRequest
|
||||
{
|
||||
public string SynergyId { get; set; } = string.Empty;
|
||||
public string CarClass { get; set; } = string.Empty;
|
||||
public string Track { get; set; } = string.Empty;
|
||||
public string GameMode { get; set; } = "casual"; // "ranked", "casual"
|
||||
}
|
||||
|
||||
// Matchmaking status response
|
||||
public class MatchmakingStatusResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public string Status { get; set; } = string.Empty; // "queued", "matched", "cancelled"
|
||||
public int? QueueId { get; set; }
|
||||
public int? SessionId { get; set; }
|
||||
public string? SessionCode { get; set; }
|
||||
public int? EstimatedWaitSeconds { get; set; }
|
||||
public DateTime QueuedAt { get; set; }
|
||||
}
|
||||
|
||||
// Create race session request
|
||||
public class CreateRaceSessionRequest
|
||||
{
|
||||
public string SynergyId { get; set; } = string.Empty;
|
||||
public string Track { get; set; } = string.Empty;
|
||||
public string CarClass { get; set; } = string.Empty;
|
||||
public int MaxPlayers { get; set; } = 8;
|
||||
public bool IsPrivate { get; set; } = false;
|
||||
}
|
||||
|
||||
// Race session DTO
|
||||
public class RaceSessionDto
|
||||
{
|
||||
public int SessionId { get; set; }
|
||||
public string SessionCode { get; set; } = string.Empty;
|
||||
public string Track { get; set; } = string.Empty;
|
||||
public string CarClass { get; set; } = string.Empty;
|
||||
public int HostUserId { get; set; }
|
||||
public string HostNickname { get; set; } = string.Empty;
|
||||
public int CurrentPlayers { get; set; }
|
||||
public int MaxPlayers { get; set; }
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
// Race session response
|
||||
public class RaceSessionResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public RaceSessionDto Session { get; set; } = new();
|
||||
public List<ParticipantDto> Participants { get; set; } = new();
|
||||
}
|
||||
|
||||
// Participant DTO
|
||||
public class ParticipantDto
|
||||
{
|
||||
public int UserId { get; set; }
|
||||
public string Nickname { get; set; } = string.Empty;
|
||||
public string SynergyId { get; set; } = string.Empty;
|
||||
public string CarId { get; set; } = string.Empty;
|
||||
public bool IsReady { get; set; }
|
||||
public int? FinishPosition { get; set; }
|
||||
public double? RaceTime { get; set; }
|
||||
}
|
||||
|
||||
// Upload ghost request
|
||||
public class UploadGhostRequest
|
||||
{
|
||||
public string SynergyId { get; set; } = string.Empty;
|
||||
public string Track { get; set; } = string.Empty;
|
||||
public string CarId { get; set; } = string.Empty;
|
||||
public double RaceTime { get; set; }
|
||||
public string TelemetryData { get; set; } = string.Empty; // Base64 or JSON
|
||||
}
|
||||
|
||||
// Ghost data DTO
|
||||
public class GhostDataDto
|
||||
{
|
||||
public int GhostId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public string Nickname { get; set; } = string.Empty;
|
||||
public string Track { get; set; } = string.Empty;
|
||||
public string CarId { get; set; } = string.Empty;
|
||||
public double RaceTime { get; set; }
|
||||
public string TelemetryData { get; set; } = string.Empty;
|
||||
public DateTime UploadedAt { get; set; }
|
||||
}
|
||||
|
||||
// Ghost data response
|
||||
public class GhostDataResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public GhostDataDto? Ghost { get; set; }
|
||||
}
|
||||
|
||||
// Submit race result request
|
||||
public class SubmitRaceResultRequest
|
||||
{
|
||||
public string SynergyId { get; set; } = string.Empty;
|
||||
public int SessionId { get; set; }
|
||||
public double RaceTime { get; set; }
|
||||
public int Position { get; set; }
|
||||
public string? TelemetryData { get; set; }
|
||||
}
|
||||
|
||||
// Race result response
|
||||
public class RaceResultResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public int Position { get; set; }
|
||||
public int RewardGold { get; set; }
|
||||
public int RewardCash { get; set; }
|
||||
public int RewardXP { get; set; }
|
||||
public int? RatingChange { get; set; } // For ranked matches
|
||||
public int? NewRating { get; set; }
|
||||
}
|
||||
|
||||
// Race results (all players)
|
||||
public class RaceResultsResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public List<ParticipantDto> Results { get; set; } = new();
|
||||
}
|
||||
|
||||
// Competitive rating DTO
|
||||
public class CompetitiveRatingDto
|
||||
{
|
||||
public int UserId { get; set; }
|
||||
public string Nickname { get; set; } = string.Empty;
|
||||
public int Rating { get; set; }
|
||||
public int Wins { get; set; }
|
||||
public int Losses { get; set; }
|
||||
public int Draws { get; set; }
|
||||
public string Division { get; set; } = string.Empty;
|
||||
public int DivisionRank { get; set; }
|
||||
}
|
||||
|
||||
// Rating response
|
||||
public class RatingResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public CompetitiveRatingDto Rating { get; set; } = new();
|
||||
}
|
||||
|
||||
// Leaderboard response
|
||||
public class CompetitiveLeaderboardResponse
|
||||
{
|
||||
public int ResultCode { get; set; } = 0;
|
||||
public string? Message { get; set; }
|
||||
public List<CompetitiveRatingDto> Leaderboard { get; set; } = new();
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("RR3CommunityServer")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+c0ddf3aa6fc17b0ad43a33dd4cd956176206e9da")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+a8d282ab362911eaef6cde7f27d7e899da73fd65")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("RR3CommunityServer")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("RR3CommunityServer")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
edd2ae211230cce6ca5372fe176eebd7993497a552197d92affb247d42049965
|
||||
8701d587c5b0d24f6fdd2ad6ddd8ff13b887210bcdd063c3ed2a8ccc825ec261
|
||||
|
||||
@@ -1 +1 @@
|
||||
35adc4026547cea5d97701a77d5dcedeee3f7c5cf662ea648e68db92bb66fd73
|
||||
6663ff8767ed452ae4cf5412b942e99434f92d354e409c2f91d2b8fc9da80d6c
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
{"documents":{"E:\\rr3\\RR3CommunityServer\\*":"https://raw.githubusercontent.com/ssfdre38/rr3-server/c0ddf3aa6fc17b0ad43a33dd4cd956176206e9da/*"}}
|
||||
{"documents":{"E:\\rr3\\RR3CommunityServer\\*":"https://raw.githubusercontent.com/ssfdre38/rr3-server/a8d282ab362911eaef6cde7f27d7e899da73fd65/*"}}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Reference in New Issue
Block a user