Initial commit: RR3 Community Server with web admin panel
- ASP.NET Core 8 REST API server - 12 API endpoints matching EA Synergy protocol - SQLite database with Entity Framework Core - Web admin panel with Bootstrap 5 - User, Catalog, Session, Purchase management - Comprehensive documentation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
49
RR3CommunityServer/Controllers/DirectorController.cs
Normal file
49
RR3CommunityServer/Controllers/DirectorController.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RR3CommunityServer.Models;
|
||||
|
||||
namespace RR3CommunityServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("director/api/android")]
|
||||
public class DirectorController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<DirectorController> _logger;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public DirectorController(ILogger<DirectorController> logger, IConfiguration configuration)
|
||||
{
|
||||
_logger = logger;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
[HttpGet("getDirectionByPackage")]
|
||||
public ActionResult<SynergyResponse<DirectorResponse>> GetDirection([FromQuery] string packageName)
|
||||
{
|
||||
_logger.LogInformation("Director request for package: {Package}", packageName);
|
||||
|
||||
var baseUrl = $"{Request.Scheme}://{Request.Host}";
|
||||
|
||||
var response = new SynergyResponse<DirectorResponse>
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = new DirectorResponse
|
||||
{
|
||||
serverUrls = new Dictionary<string, string>
|
||||
{
|
||||
{ "synergy.product", baseUrl },
|
||||
{ "synergy.drm", baseUrl },
|
||||
{ "synergy.user", baseUrl },
|
||||
{ "synergy.tracking", baseUrl },
|
||||
{ "synergy.s2s", baseUrl },
|
||||
{ "nexus.portal", baseUrl },
|
||||
{ "ens.url", baseUrl }
|
||||
},
|
||||
environment = "COMMUNITY",
|
||||
version = "1.0.0"
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
76
RR3CommunityServer/Controllers/DrmController.cs
Normal file
76
RR3CommunityServer/Controllers/DrmController.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RR3CommunityServer.Models;
|
||||
using RR3CommunityServer.Services;
|
||||
|
||||
namespace RR3CommunityServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("drm/api")]
|
||||
public class DrmController : ControllerBase
|
||||
{
|
||||
private readonly IDrmService _drmService;
|
||||
private readonly ILogger<DrmController> _logger;
|
||||
|
||||
public DrmController(IDrmService drmService, ILogger<DrmController> logger)
|
||||
{
|
||||
_drmService = drmService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet("core/getNonce")]
|
||||
public async Task<ActionResult<SynergyResponse<DrmNonceResponse>>> GetNonce()
|
||||
{
|
||||
_logger.LogInformation("GetNonce request");
|
||||
|
||||
var nonce = await _drmService.GenerateNonce();
|
||||
|
||||
var response = new SynergyResponse<DrmNonceResponse>
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = new DrmNonceResponse
|
||||
{
|
||||
nonce = nonce,
|
||||
expiresAt = DateTimeOffset.UtcNow.AddMinutes(5).ToUnixTimeSeconds()
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpGet("core/getPurchasedItems")]
|
||||
public async Task<ActionResult<SynergyResponse<List<PurchasedItem>>>> GetPurchasedItems()
|
||||
{
|
||||
var synergyId = HttpContext.Items["EAM-USER-ID"]?.ToString() ?? "default";
|
||||
_logger.LogInformation("GetPurchasedItems for user: {SynergyId}", synergyId);
|
||||
|
||||
var purchases = await _drmService.GetPurchasedItems(synergyId);
|
||||
|
||||
var response = new SynergyResponse<List<PurchasedItem>>
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = purchases
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost("android/verifyAndRecordPurchase")]
|
||||
public async Task<ActionResult<SynergyResponse<object>>> VerifyPurchase([FromBody] PurchaseVerificationRequest request)
|
||||
{
|
||||
var synergyId = HttpContext.Items["EAM-USER-ID"]?.ToString() ?? "default";
|
||||
_logger.LogInformation("VerifyAndRecordPurchase: user={User}, sku={Sku}", synergyId, request.sku);
|
||||
|
||||
var verified = await _drmService.VerifyAndRecordPurchase(synergyId, request);
|
||||
|
||||
var response = new SynergyResponse<object>
|
||||
{
|
||||
resultCode = verified ? 0 : -1,
|
||||
message = verified ? "Purchase verified" : "Purchase verification failed",
|
||||
data = new { verified = verified }
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
71
RR3CommunityServer/Controllers/ProductController.cs
Normal file
71
RR3CommunityServer/Controllers/ProductController.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RR3CommunityServer.Models;
|
||||
using RR3CommunityServer.Services;
|
||||
|
||||
namespace RR3CommunityServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("product/api/core")]
|
||||
public class ProductController : ControllerBase
|
||||
{
|
||||
private readonly ICatalogService _catalogService;
|
||||
private readonly ILogger<ProductController> _logger;
|
||||
|
||||
public ProductController(ICatalogService catalogService, ILogger<ProductController> logger)
|
||||
{
|
||||
_catalogService = catalogService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet("getAvailableItems")]
|
||||
public async Task<ActionResult<SynergyResponse<List<CatalogItem>>>> GetAvailableItems()
|
||||
{
|
||||
_logger.LogInformation("GetAvailableItems request");
|
||||
|
||||
var items = await _catalogService.GetAvailableItems();
|
||||
|
||||
var response = new SynergyResponse<List<CatalogItem>>
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = items
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpGet("getMTXGameCategories")]
|
||||
public async Task<ActionResult<SynergyResponse<List<CatalogCategory>>>> GetCategories()
|
||||
{
|
||||
_logger.LogInformation("GetMTXGameCategories request");
|
||||
|
||||
var categories = await _catalogService.GetCategories();
|
||||
|
||||
var response = new SynergyResponse<List<CatalogCategory>>
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = categories
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost("getDownloadItemUrl")]
|
||||
public async Task<ActionResult<SynergyResponse<object>>> GetDownloadUrl([FromBody] Dictionary<string, string> request)
|
||||
{
|
||||
var itemId = request.GetValueOrDefault("itemId", "");
|
||||
_logger.LogInformation("GetDownloadItemUrl: {ItemId}", itemId);
|
||||
|
||||
var url = await _catalogService.GetDownloadUrl(itemId);
|
||||
|
||||
var response = new SynergyResponse<object>
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = new { downloadUrl = url }
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
49
RR3CommunityServer/Controllers/TrackingController.cs
Normal file
49
RR3CommunityServer/Controllers/TrackingController.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RR3CommunityServer.Models;
|
||||
|
||||
namespace RR3CommunityServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("tracking/api/core")]
|
||||
public class TrackingController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<TrackingController> _logger;
|
||||
|
||||
public TrackingController(ILogger<TrackingController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPost("logEvent")]
|
||||
public ActionResult<SynergyResponse<object>> LogEvent([FromBody] TrackingEvent trackingEvent)
|
||||
{
|
||||
_logger.LogInformation("Tracking Event: {EventType} at {Timestamp}",
|
||||
trackingEvent.eventType,
|
||||
trackingEvent.timestamp);
|
||||
|
||||
// For community server, we just log and accept all events
|
||||
var response = new SynergyResponse<object>
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Event logged",
|
||||
data = new { received = true }
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpPost("logEvents")]
|
||||
public ActionResult<SynergyResponse<object>> LogEvents([FromBody] List<TrackingEvent> events)
|
||||
{
|
||||
_logger.LogInformation("Tracking Batch: {Count} events", events.Count);
|
||||
|
||||
var response = new SynergyResponse<object>
|
||||
{
|
||||
resultCode = 0,
|
||||
message = $"{events.Count} events logged",
|
||||
data = new { received = events.Count }
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
85
RR3CommunityServer/Controllers/UserController.cs
Normal file
85
RR3CommunityServer/Controllers/UserController.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RR3CommunityServer.Models;
|
||||
using RR3CommunityServer.Services;
|
||||
|
||||
namespace RR3CommunityServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("user/api/android")]
|
||||
public class UserController : ControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly ISessionService _sessionService;
|
||||
private readonly ILogger<UserController> _logger;
|
||||
|
||||
public UserController(IUserService userService, ISessionService sessionService, ILogger<UserController> logger)
|
||||
{
|
||||
_userService = userService;
|
||||
_sessionService = sessionService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet("getDeviceID")]
|
||||
public async Task<ActionResult<SynergyResponse<DeviceIdResponse>>> GetDeviceId(
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] string hardwareId = "")
|
||||
{
|
||||
_logger.LogInformation("GetDeviceID request: existing={Existing}, hardware={Hardware}", deviceId, hardwareId);
|
||||
|
||||
var newDeviceId = await _userService.GetOrCreateDeviceId(deviceId, hardwareId);
|
||||
var synergyId = await _userService.GetOrCreateSynergyId(newDeviceId);
|
||||
var sessionId = await _sessionService.CreateSession(synergyId);
|
||||
|
||||
var response = new SynergyResponse<DeviceIdResponse>
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = new DeviceIdResponse
|
||||
{
|
||||
deviceId = newDeviceId,
|
||||
synergyId = synergyId,
|
||||
timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpGet("validateDeviceID")]
|
||||
public async Task<ActionResult<SynergyResponse<object>>> ValidateDeviceId([FromQuery] string deviceId)
|
||||
{
|
||||
_logger.LogInformation("ValidateDeviceID: {DeviceId}", deviceId);
|
||||
|
||||
var result = await _userService.ValidateDeviceId(deviceId);
|
||||
|
||||
var response = new SynergyResponse<object>
|
||||
{
|
||||
resultCode = result == "valid" ? 0 : -1,
|
||||
message = result == "valid" ? "Device validated" : "Device not found",
|
||||
data = new { status = result }
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
[HttpGet("getAnonUid")]
|
||||
public async Task<ActionResult<SynergyResponse<AnonUidResponse>>> GetAnonUid()
|
||||
{
|
||||
_logger.LogInformation("GetAnonUid request");
|
||||
|
||||
var anonUid = await _userService.GetOrCreateAnonUid();
|
||||
|
||||
var response = new SynergyResponse<AnonUidResponse>
|
||||
{
|
||||
resultCode = 0,
|
||||
message = "Success",
|
||||
data = new AnonUidResponse
|
||||
{
|
||||
anonUid = anonUid,
|
||||
expiresAt = DateTimeOffset.UtcNow.AddDays(30).ToUnixTimeSeconds()
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
106
RR3CommunityServer/Data/RR3DbContext.cs
Normal file
106
RR3CommunityServer/Data/RR3DbContext.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace RR3CommunityServer.Data;
|
||||
|
||||
public class RR3DbContext : DbContext
|
||||
{
|
||||
public RR3DbContext(DbContextOptions<RR3DbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<Device> Devices { get; set; }
|
||||
public DbSet<User> Users { get; set; }
|
||||
public DbSet<Session> Sessions { get; set; }
|
||||
public DbSet<Purchase> Purchases { get; set; }
|
||||
public DbSet<CatalogItem> CatalogItems { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// Seed some default catalog items
|
||||
modelBuilder.Entity<CatalogItem>().HasData(
|
||||
new CatalogItem
|
||||
{
|
||||
Id = 1,
|
||||
Sku = "com.ea.rr3.gold_1000",
|
||||
Name = "1000 Gold",
|
||||
Type = "currency",
|
||||
Price = 0.99m,
|
||||
Available = true
|
||||
},
|
||||
new CatalogItem
|
||||
{
|
||||
Id = 2,
|
||||
Sku = "com.ea.rr3.car_tier1",
|
||||
Name = "Starter Car",
|
||||
Type = "car",
|
||||
Price = 0m,
|
||||
Available = true
|
||||
},
|
||||
new CatalogItem
|
||||
{
|
||||
Id = 3,
|
||||
Sku = "com.ea.rr3.upgrade_engine",
|
||||
Name = "Engine Upgrade",
|
||||
Type = "upgrade",
|
||||
Price = 4.99m,
|
||||
Available = true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Database entities
|
||||
public class Device
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
public string HardwareId { get; set; } = string.Empty;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime LastSeenAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class User
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string SynergyId { get; set; } = string.Empty;
|
||||
public string? DeviceId { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public string? Nickname { get; set; }
|
||||
}
|
||||
|
||||
public class Session
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string SessionId { get; set; } = string.Empty;
|
||||
public string? SynergyId { get; set; }
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
public int? UserId { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
}
|
||||
|
||||
public class Purchase
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string SynergyId { get; set; } = string.Empty;
|
||||
public string ItemId { get; set; } = string.Empty;
|
||||
public string Sku { get; set; } = string.Empty;
|
||||
public string OrderId { get; set; } = string.Empty;
|
||||
public DateTime PurchaseTime { get; set; } = DateTime.UtcNow;
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public decimal Price { get; set; }
|
||||
public string Status { get; set; } = "approved";
|
||||
// For web panel display
|
||||
public int? UserId { get; set; }
|
||||
public DateTime PurchaseDate => PurchaseTime;
|
||||
}
|
||||
|
||||
public class CatalogItem
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Sku { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public decimal Price { get; set; }
|
||||
public bool Available { get; set; } = true;
|
||||
}
|
||||
76
RR3CommunityServer/Middleware/SynergyMiddleware.cs
Normal file
76
RR3CommunityServer/Middleware/SynergyMiddleware.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
namespace RR3CommunityServer.Middleware;
|
||||
|
||||
public class SynergyHeadersMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<SynergyHeadersMiddleware> _logger;
|
||||
|
||||
public SynergyHeadersMiddleware(RequestDelegate next, ILogger<SynergyHeadersMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
// Log incoming Synergy headers
|
||||
var sessionId = context.Request.Headers["EAM-SESSION"].FirstOrDefault();
|
||||
var userId = context.Request.Headers["EAM-USER-ID"].FirstOrDefault();
|
||||
var sellId = context.Request.Headers["EA-SELL-ID"].FirstOrDefault();
|
||||
var sdkVersion = context.Request.Headers["SDK-VERSION"].FirstOrDefault();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Synergy Request: Path={Path}, Session={Session}, User={User}, Sell={Sell}, SDK={SDK}",
|
||||
context.Request.Path,
|
||||
sessionId ?? "none",
|
||||
userId ?? "none",
|
||||
sellId ?? "none",
|
||||
sdkVersion ?? "none"
|
||||
);
|
||||
|
||||
// Store in context for controllers
|
||||
context.Items["EAM-SESSION"] = sessionId;
|
||||
context.Items["EAM-USER-ID"] = userId;
|
||||
context.Items["EA-SELL-ID"] = sellId;
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
|
||||
public class SessionValidationMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<SessionValidationMiddleware> _logger;
|
||||
|
||||
// Paths that don't require session validation
|
||||
private static readonly HashSet<string> PublicPaths = new()
|
||||
{
|
||||
"/director/api/android/getDirectionByPackage",
|
||||
"/user/api/android/getDeviceID",
|
||||
"/user/api/android/getAnonUid",
|
||||
"/swagger",
|
||||
"/health"
|
||||
};
|
||||
|
||||
public SessionValidationMiddleware(RequestDelegate next, ILogger<SessionValidationMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
var path = context.Request.Path.Value ?? "";
|
||||
|
||||
// Skip validation for public paths
|
||||
if (PublicPaths.Any(p => path.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
// For now, allow all requests (lenient for community server)
|
||||
// In production, validate session here
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
90
RR3CommunityServer/Models/ApiModels.cs
Normal file
90
RR3CommunityServer/Models/ApiModels.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
namespace RR3CommunityServer.Models;
|
||||
|
||||
// Standard Synergy API response wrapper
|
||||
public class SynergyResponse<T>
|
||||
{
|
||||
public int resultCode { get; set; } = 0; // 0 = success, negative = error
|
||||
public string? message { get; set; }
|
||||
public T? data { get; set; }
|
||||
}
|
||||
|
||||
// User models
|
||||
public class DeviceIdResponse
|
||||
{
|
||||
public string deviceId { get; set; } = string.Empty;
|
||||
public string synergyId { get; set; } = string.Empty;
|
||||
public long timestamp { get; set; }
|
||||
}
|
||||
|
||||
public class AnonUidResponse
|
||||
{
|
||||
public string anonUid { get; set; } = string.Empty;
|
||||
public long expiresAt { get; set; }
|
||||
}
|
||||
|
||||
// Product/Catalog models
|
||||
public class CatalogItem
|
||||
{
|
||||
public string itemId { get; set; } = string.Empty;
|
||||
public string sku { get; set; } = string.Empty;
|
||||
public string name { get; set; } = string.Empty;
|
||||
public string description { get; set; } = string.Empty;
|
||||
public string category { get; set; } = string.Empty;
|
||||
public decimal price { get; set; }
|
||||
public string currency { get; set; } = "USD";
|
||||
public Dictionary<string, object> metadata { get; set; } = new();
|
||||
}
|
||||
|
||||
public class CatalogCategory
|
||||
{
|
||||
public string categoryId { get; set; } = string.Empty;
|
||||
public string name { get; set; } = string.Empty;
|
||||
public List<string> itemIds { get; set; } = new();
|
||||
}
|
||||
|
||||
// DRM models
|
||||
public class DrmNonceResponse
|
||||
{
|
||||
public string nonce { get; set; } = string.Empty;
|
||||
public long expiresAt { get; set; }
|
||||
}
|
||||
|
||||
public class PurchasedItem
|
||||
{
|
||||
public string itemId { get; set; } = string.Empty;
|
||||
public string sku { get; set; } = string.Empty;
|
||||
public string orderId { get; set; } = string.Empty;
|
||||
public long purchaseTime { get; set; }
|
||||
public string token { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class PurchaseVerificationRequest
|
||||
{
|
||||
public string receipt { get; set; } = string.Empty;
|
||||
public string signature { get; set; } = string.Empty;
|
||||
public string sku { get; set; } = string.Empty;
|
||||
public string orderId { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
// Tracking models
|
||||
public class TrackingEvent
|
||||
{
|
||||
public string eventType { get; set; } = string.Empty;
|
||||
public long timestamp { get; set; }
|
||||
public Dictionary<string, object> properties { get; set; } = new();
|
||||
}
|
||||
|
||||
// Director/Service Discovery
|
||||
public class DirectorResponse
|
||||
{
|
||||
public Dictionary<string, string> serverUrls { get; set; } = new()
|
||||
{
|
||||
{ "synergy.product", "https://localhost:5001" },
|
||||
{ "synergy.drm", "https://localhost:5001" },
|
||||
{ "synergy.user", "https://localhost:5001" },
|
||||
{ "synergy.tracking", "https://localhost:5001" },
|
||||
{ "synergy.s2s", "https://localhost:5001" }
|
||||
};
|
||||
public string environment { get; set; } = "COMMUNITY";
|
||||
public string version { get; set; } = "1.0.0";
|
||||
}
|
||||
225
RR3CommunityServer/Pages/Admin.cshtml
Normal file
225
RR3CommunityServer/Pages/Admin.cshtml
Normal file
@@ -0,0 +1,225 @@
|
||||
@page "/admin"
|
||||
@model RR3CommunityServer.Pages.AdminModel
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
ViewData["Title"] = "Dashboard";
|
||||
}
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<!-- Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="display-4">🏎️ RR3 Community Server</h1>
|
||||
<p class="text-muted">Administration Dashboard</p>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="badge bg-success fs-6">🟢 Server Online</div>
|
||||
<div class="text-muted small mt-1">Uptime: @Model.Uptime</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics Cards -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Total Users</h6>
|
||||
<h2 class="mb-0">@Model.TotalUsers</h2>
|
||||
</div>
|
||||
<div class="fs-1 text-primary">👥</div>
|
||||
</div>
|
||||
<small class="text-muted">Registered players</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card border-success">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Active Sessions</h6>
|
||||
<h2 class="mb-0">@Model.ActiveSessions</h2>
|
||||
</div>
|
||||
<div class="fs-1 text-success">🔄</div>
|
||||
</div>
|
||||
<small class="text-muted">Currently online</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card border-info">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Total Devices</h6>
|
||||
<h2 class="mb-0">@Model.TotalDevices</h2>
|
||||
</div>
|
||||
<div class="fs-1 text-info">📱</div>
|
||||
</div>
|
||||
<small class="text-muted">Registered devices</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card border-warning">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="text-muted mb-0">Catalog Items</h6>
|
||||
<h2 class="mb-0">@Model.TotalCatalogItems</h2>
|
||||
</div>
|
||||
<div class="fs-1 text-warning">🏪</div>
|
||||
</div>
|
||||
<small class="text-muted">Available items</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-dark text-white">
|
||||
<h5 class="mb-0">⚡ Quick Actions</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
<a href="/admin/users" class="btn btn-primary">
|
||||
<i class="bi bi-people"></i> Manage Users
|
||||
</a>
|
||||
<a href="/admin/catalog" class="btn btn-info">
|
||||
<i class="bi bi-shop"></i> Manage Catalog
|
||||
</a>
|
||||
<a href="/admin/sessions" class="btn btn-success">
|
||||
<i class="bi bi-clock-history"></i> View Sessions
|
||||
</a>
|
||||
<a href="/admin/purchases" class="btn btn-warning">
|
||||
<i class="bi bi-cart"></i> View Purchases
|
||||
</a>
|
||||
<a href="/admin/settings" class="btn btn-secondary">
|
||||
<i class="bi bi-gear"></i> Server Settings
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">📊 Recent Users</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Synergy ID</th>
|
||||
<th>Joined</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var user in Model.RecentUsers)
|
||||
{
|
||||
<tr>
|
||||
<td><code>@user.SynergyId</code></td>
|
||||
<td><small>@user.CreatedAt.ToString("g")</small></td>
|
||||
<td>
|
||||
<a href="/admin/users?id=@user.Id" class="btn btn-sm btn-outline-primary">View</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a href="/admin/users" class="btn btn-sm btn-link">View All Users →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0">🔄 Active Sessions</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Session ID</th>
|
||||
<th>Expires</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var session in Model.RecentSessions)
|
||||
{
|
||||
<tr>
|
||||
<td><code>@session.SessionId.Substring(0, 8)...</code></td>
|
||||
<td><small>@session.ExpiresAt.ToString("g")</small></td>
|
||||
<td><span class="badge bg-success">Active</span></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a href="/admin/sessions" class="btn btn-sm btn-link">View All Sessions →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Server Info -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-dark text-white">
|
||||
<h5 class="mb-0">ℹ️ Server Information</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">Server URL:</dt>
|
||||
<dd class="col-sm-8"><code>@Model.ServerUrl</code></dd>
|
||||
|
||||
<dt class="col-sm-4">Platform:</dt>
|
||||
<dd class="col-sm-8">@Model.Platform</dd>
|
||||
|
||||
<dt class="col-sm-4">.NET Version:</dt>
|
||||
<dd class="col-sm-8">@Model.DotNetVersion</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">Database:</dt>
|
||||
<dd class="col-sm-8">SQLite (EF Core)</dd>
|
||||
|
||||
<dt class="col-sm-4">API Endpoints:</dt>
|
||||
<dd class="col-sm-8">12 active</dd>
|
||||
|
||||
<dt class="col-sm-4">Swagger:</dt>
|
||||
<dd class="col-sm-8"><a href="/swagger" target="_blank">View API Docs</a></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
58
RR3CommunityServer/Pages/Admin.cshtml.cs
Normal file
58
RR3CommunityServer/Pages/Admin.cshtml.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RR3CommunityServer.Data;
|
||||
using static RR3CommunityServer.Data.RR3DbContext;
|
||||
|
||||
namespace RR3CommunityServer.Pages;
|
||||
|
||||
public class AdminModel : PageModel
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
|
||||
public AdminModel(RR3DbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public int TotalUsers { get; set; }
|
||||
public int ActiveSessions { get; set; }
|
||||
public int TotalDevices { get; set; }
|
||||
public int TotalCatalogItems { get; set; }
|
||||
public string Uptime { get; set; } = "0:00:00";
|
||||
public string ServerUrl { get; set; } = string.Empty;
|
||||
public string Platform { get; set; } = string.Empty;
|
||||
public string DotNetVersion { get; set; } = string.Empty;
|
||||
|
||||
public List<User> RecentUsers { get; set; } = new();
|
||||
public List<Session> RecentSessions { get; set; } = new();
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
// Get statistics
|
||||
TotalUsers = await _context.Users.CountAsync();
|
||||
TotalDevices = await _context.Devices.CountAsync();
|
||||
TotalCatalogItems = await _context.CatalogItems.CountAsync();
|
||||
ActiveSessions = await _context.Sessions
|
||||
.Where(s => s.ExpiresAt > DateTime.UtcNow)
|
||||
.CountAsync();
|
||||
|
||||
// Get recent activity
|
||||
RecentUsers = await _context.Users
|
||||
.OrderByDescending(u => u.CreatedAt)
|
||||
.Take(5)
|
||||
.ToListAsync();
|
||||
|
||||
RecentSessions = await _context.Sessions
|
||||
.Where(s => s.ExpiresAt > DateTime.UtcNow)
|
||||
.OrderByDescending(s => s.CreatedAt)
|
||||
.Take(5)
|
||||
.ToListAsync();
|
||||
|
||||
// Server info
|
||||
var uptime = DateTime.UtcNow - System.Diagnostics.Process.GetCurrentProcess().StartTime.ToUniversalTime();
|
||||
Uptime = $"{uptime.Days}d {uptime.Hours}h {uptime.Minutes}m";
|
||||
ServerUrl = $"{Request.Scheme}://{Request.Host}";
|
||||
Platform = Environment.OSVersion.Platform.ToString();
|
||||
DotNetVersion = Environment.Version.ToString();
|
||||
}
|
||||
}
|
||||
201
RR3CommunityServer/Pages/Catalog.cshtml
Normal file
201
RR3CommunityServer/Pages/Catalog.cshtml
Normal file
@@ -0,0 +1,201 @@
|
||||
@page
|
||||
@model RR3CommunityServer.Pages.CatalogModel
|
||||
@{
|
||||
ViewData["Title"] = "Catalog Management";
|
||||
}
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1>🏪 Catalog Management</h1>
|
||||
<p class="text-muted">Manage in-game items and purchases</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#addItemModal">
|
||||
<i class="bi bi-plus-circle"></i> Add New Item
|
||||
</button>
|
||||
<a href="/admin" class="btn btn-outline-secondary">← Back</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0">Catalog Items (@Model.CatalogItems.Count)</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>SKU</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Price</th>
|
||||
<th>Available</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.CatalogItems)
|
||||
{
|
||||
<tr>
|
||||
<td><code>@item.Sku</code></td>
|
||||
<td><strong>@item.Name</strong></td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">@item.Type</span>
|
||||
</td>
|
||||
<td>
|
||||
@if (item.Price == 0)
|
||||
{
|
||||
<span class="text-success"><strong>FREE</strong></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@item.Price.ToString("C2")</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (item.Available)
|
||||
{
|
||||
<span class="badge bg-success">✓ Yes</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-danger">✗ No</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#editModal@(item.Id)">
|
||||
<i class="bi bi-pencil"></i> Edit
|
||||
</button>
|
||||
<form method="post" asp-page-handler="ToggleAvailability" class="d-inline">
|
||||
<input type="hidden" name="itemId" value="@item.Id" />
|
||||
<button type="submit" class="btn btn-sm btn-@(item.Available ? "warning" : "success")">
|
||||
<i class="bi bi-@(item.Available ? "eye-slash" : "eye")"></i> @(item.Available ? "Disable" : "Enable")
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" asp-page-handler="Delete" class="d-inline">
|
||||
<input type="hidden" name="itemId" value="@item.Id" />
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Delete this item?')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal@(item.Id)" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form method="post" asp-page-handler="Update">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title">Edit Item</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="itemId" value="@item.Id" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">SKU</label>
|
||||
<input type="text" name="sku" value="@item.Sku" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" name="name" value="@item.Name" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Type</label>
|
||||
<select name="type" class="form-select" required>
|
||||
<option value="car" selected="@(item.Type == "car")">Car</option>
|
||||
<option value="upgrade" selected="@(item.Type == "upgrade")">Upgrade</option>
|
||||
<option value="currency" selected="@(item.Type == "currency")">Currency</option>
|
||||
<option value="consumable" selected="@(item.Type == "consumable")">Consumable</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Price</label>
|
||||
<input type="number" name="price" value="@item.Price" step="0.01" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" name="available" checked="@item.Available" class="form-check-input" id="available@(item.Id)">
|
||||
<label class="form-check-label" for="available@(item.Id)">Available for purchase</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add New Item Modal -->
|
||||
<div class="modal fade" id="addItemModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form method="post" asp-page-handler="Add">
|
||||
<div class="modal-header bg-success text-white">
|
||||
<h5 class="modal-title">Add New Item</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">SKU</label>
|
||||
<input type="text" name="sku" class="form-control" placeholder="com.ea.rr3.car.porsche911" required>
|
||||
<small class="text-muted">Unique identifier (e.g., com.ea.rr3.car.name)</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" name="name" class="form-control" placeholder="Porsche 911 GT3" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Type</label>
|
||||
<select name="type" class="form-select" required>
|
||||
<option value="car">Car</option>
|
||||
<option value="upgrade">Upgrade</option>
|
||||
<option value="currency">Currency</option>
|
||||
<option value="consumable">Consumable</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Price</label>
|
||||
<input type="number" name="price" step="0.01" value="0.00" class="form-control" required>
|
||||
<small class="text-muted">Set to 0 for free items</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" name="available" checked class="form-check-input" id="availableNew">
|
||||
<label class="form-check-label" for="availableNew">Available for purchase</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-success">Add Item</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
84
RR3CommunityServer/Pages/Catalog.cshtml.cs
Normal file
84
RR3CommunityServer/Pages/Catalog.cshtml.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RR3CommunityServer.Data;
|
||||
using static RR3CommunityServer.Data.RR3DbContext;
|
||||
|
||||
namespace RR3CommunityServer.Pages;
|
||||
|
||||
public class CatalogModel : PageModel
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
|
||||
public CatalogModel(RR3DbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public List<CatalogItem> CatalogItems { get; set; } = new();
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
CatalogItems = await _context.CatalogItems
|
||||
.OrderBy(c => c.Type)
|
||||
.ThenBy(c => c.Name)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAddAsync(string sku, string name, string type, decimal price, bool available)
|
||||
{
|
||||
var item = new CatalogItem
|
||||
{
|
||||
Sku = sku,
|
||||
Name = name,
|
||||
Type = type,
|
||||
Price = price,
|
||||
Available = available
|
||||
};
|
||||
|
||||
_context.CatalogItems.Add(item);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostUpdateAsync(int itemId, string sku, string name, string type, decimal price, bool available)
|
||||
{
|
||||
var item = await _context.CatalogItems.FindAsync(itemId);
|
||||
if (item != null)
|
||||
{
|
||||
item.Sku = sku;
|
||||
item.Name = name;
|
||||
item.Type = type;
|
||||
item.Price = price;
|
||||
item.Available = available;
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostToggleAvailabilityAsync(int itemId)
|
||||
{
|
||||
var item = await _context.CatalogItems.FindAsync(itemId);
|
||||
if (item != null)
|
||||
{
|
||||
item.Available = !item.Available;
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteAsync(int itemId)
|
||||
{
|
||||
var item = await _context.CatalogItems.FindAsync(itemId);
|
||||
if (item != null)
|
||||
{
|
||||
_context.CatalogItems.Remove(item);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
185
RR3CommunityServer/Pages/Purchases.cshtml
Normal file
185
RR3CommunityServer/Pages/Purchases.cshtml
Normal file
@@ -0,0 +1,185 @@
|
||||
@page
|
||||
@model RR3CommunityServer.Pages.PurchasesModel
|
||||
@{
|
||||
ViewData["Title"] = "Purchase History";
|
||||
}
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1>🛒 Purchase History</h1>
|
||||
<p class="text-muted">View all in-game purchases</p>
|
||||
</div>
|
||||
<a href="/admin" class="btn btn-outline-secondary">← Back to Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Purchases.Count == 0)
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> No purchases yet. Purchases will appear here when players buy items.
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Statistics -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted">Total Purchases</h6>
|
||||
<h2 class="text-primary">@Model.Purchases.Count</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-success">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted">Approved</h6>
|
||||
<h2 class="text-success">@Model.Purchases.Count(p => p.Status == "approved")</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-info">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted">Total Value</h6>
|
||||
<h2 class="text-info">@Model.TotalValue.ToString("C2")</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Purchase List -->
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">All Purchases</h5>
|
||||
<form method="get" class="d-flex gap-2">
|
||||
<input type="text" name="search" value="@Model.SearchQuery" class="form-control form-control-sm" placeholder="Search by SKU or User...">
|
||||
<button type="submit" class="btn btn-sm btn-dark">Search</button>
|
||||
@if (!string.IsNullOrEmpty(Model.SearchQuery))
|
||||
{
|
||||
<a href="/admin/purchases" class="btn btn-sm btn-outline-dark">Clear</a>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>User</th>
|
||||
<th>SKU</th>
|
||||
<th>Price</th>
|
||||
<th>Status</th>
|
||||
<th>Purchase Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var purchase in Model.Purchases)
|
||||
{
|
||||
<tr>
|
||||
<td><strong>@purchase.Id</strong></td>
|
||||
<td>
|
||||
@if (purchase.UserId.HasValue)
|
||||
{
|
||||
<code>User @purchase.UserId</code>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">Unknown</span>
|
||||
}
|
||||
</td>
|
||||
<td><code>@purchase.Sku</code></td>
|
||||
<td>
|
||||
@if (purchase.Price == 0)
|
||||
{
|
||||
<span class="text-success"><strong>FREE</strong></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@purchase.Price.ToString("C2")</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (purchase.Status == "approved")
|
||||
{
|
||||
<span class="badge bg-success">✓ Approved</span>
|
||||
}
|
||||
else if (purchase.Status == "pending")
|
||||
{
|
||||
<span class="badge bg-warning">⏳ Pending</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-danger">✗ @purchase.Status</span>
|
||||
}
|
||||
</td>
|
||||
<td>@purchase.PurchaseDate.ToString("g")</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" data-bs-toggle="modal" data-bs-target="#purchaseModal@(purchase.Id)">
|
||||
<i class="bi bi-eye"></i> Details
|
||||
</button>
|
||||
<form method="post" asp-page-handler="Delete" class="d-inline">
|
||||
<input type="hidden" name="purchaseId" value="@purchase.Id" />
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Delete this purchase record?')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Purchase Details Modal -->
|
||||
<div class="modal fade" id="purchaseModal@(purchase.Id)" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-warning">
|
||||
<h5 class="modal-title">Purchase Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-3">Purchase ID:</dt>
|
||||
<dd class="col-sm-9">@purchase.Id</dd>
|
||||
|
||||
<dt class="col-sm-3">User ID:</dt>
|
||||
<dd class="col-sm-9">@(purchase.UserId?.ToString() ?? "N/A")</dd>
|
||||
|
||||
<dt class="col-sm-3">SKU:</dt>
|
||||
<dd class="col-sm-9"><code>@purchase.Sku</code></dd>
|
||||
|
||||
<dt class="col-sm-3">Price:</dt>
|
||||
<dd class="col-sm-9">@purchase.Price.ToString("C2")</dd>
|
||||
|
||||
<dt class="col-sm-3">Status:</dt>
|
||||
<dd class="col-sm-9">
|
||||
<span class="badge bg-@(purchase.Status == "approved" ? "success" : "warning")">
|
||||
@purchase.Status
|
||||
</span>
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-3">Purchase Date:</dt>
|
||||
<dd class="col-sm-9">@purchase.PurchaseDate.ToString("F")</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
52
RR3CommunityServer/Pages/Purchases.cshtml.cs
Normal file
52
RR3CommunityServer/Pages/Purchases.cshtml.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RR3CommunityServer.Data;
|
||||
using static RR3CommunityServer.Data.RR3DbContext;
|
||||
|
||||
namespace RR3CommunityServer.Pages;
|
||||
|
||||
public class PurchasesModel : PageModel
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
|
||||
public PurchasesModel(RR3DbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public List<Purchase> Purchases { get; set; } = new();
|
||||
public decimal TotalValue { get; set; }
|
||||
public string? SearchQuery { get; set; }
|
||||
|
||||
public async Task OnGetAsync(string? search)
|
||||
{
|
||||
SearchQuery = search;
|
||||
|
||||
var query = _context.Purchases.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(search))
|
||||
{
|
||||
query = query.Where(p => p.Sku.Contains(search) ||
|
||||
(p.UserId != null && p.UserId.ToString()!.Contains(search)));
|
||||
}
|
||||
|
||||
Purchases = await query
|
||||
.OrderByDescending(p => p.PurchaseDate)
|
||||
.ToListAsync();
|
||||
|
||||
TotalValue = Purchases.Sum(p => p.Price);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteAsync(int purchaseId)
|
||||
{
|
||||
var purchase = await _context.Purchases.FindAsync(purchaseId);
|
||||
if (purchase != null)
|
||||
{
|
||||
_context.Purchases.Remove(purchase);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
160
RR3CommunityServer/Pages/Sessions.cshtml
Normal file
160
RR3CommunityServer/Pages/Sessions.cshtml
Normal file
@@ -0,0 +1,160 @@
|
||||
@page
|
||||
@model RR3CommunityServer.Pages.SessionsModel
|
||||
@{
|
||||
ViewData["Title"] = "Session Management";
|
||||
}
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1>🔄 Session Management</h1>
|
||||
<p class="text-muted">Monitor active and expired sessions</p>
|
||||
</div>
|
||||
<div>
|
||||
<form method="post" asp-page-handler="CleanupExpired" class="d-inline">
|
||||
<button type="submit" class="btn btn-warning">
|
||||
<i class="bi bi-trash3"></i> Cleanup Expired
|
||||
</button>
|
||||
</form>
|
||||
<a href="/admin" class="btn btn-outline-secondary">← Back</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card border-success">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted">Active Sessions</h6>
|
||||
<h2 class="text-success">@Model.ActiveSessions.Count</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-danger">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted">Expired Sessions</h6>
|
||||
<h2 class="text-danger">@Model.ExpiredSessions.Count</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-info">
|
||||
<div class="card-body">
|
||||
<h6 class="text-muted">Total Sessions</h6>
|
||||
<h2 class="text-info">@Model.AllSessions.Count</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active Sessions -->
|
||||
@if (Model.ActiveSessions.Any())
|
||||
{
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0">🟢 Active Sessions (@Model.ActiveSessions.Count)</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Session ID</th>
|
||||
<th>User ID</th>
|
||||
<th>Device ID</th>
|
||||
<th>Created</th>
|
||||
<th>Expires</th>
|
||||
<th>Time Left</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var session in Model.ActiveSessions)
|
||||
{
|
||||
var timeLeft = session.ExpiresAt - DateTime.UtcNow;
|
||||
<tr>
|
||||
<td><code>@session.SessionId.Substring(0, 12)...</code></td>
|
||||
<td>@session.UserId</td>
|
||||
<td><code>@session.DeviceId.Substring(0, 12)...</code></td>
|
||||
<td>@session.CreatedAt.ToString("g")</td>
|
||||
<td>@session.ExpiresAt.ToString("g")</td>
|
||||
<td>
|
||||
<span class="badge bg-success">
|
||||
@((int)timeLeft.TotalHours)h @timeLeft.Minutes)m
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" asp-page-handler="Delete" class="d-inline">
|
||||
<input type="hidden" name="sessionId" value="@session.Id" />
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Terminate this session?')">
|
||||
<i class="bi bi-x-circle"></i> Terminate
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Expired Sessions -->
|
||||
@if (Model.ExpiredSessions.Any())
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header bg-secondary text-white">
|
||||
<h5 class="mb-0">⚫ Expired Sessions (@Model.ExpiredSessions.Count)</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Session ID</th>
|
||||
<th>User ID</th>
|
||||
<th>Device ID</th>
|
||||
<th>Created</th>
|
||||
<th>Expired</th>
|
||||
<th>Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var session in Model.ExpiredSessions.Take(20))
|
||||
{
|
||||
var duration = session.ExpiresAt - session.CreatedAt;
|
||||
<tr class="text-muted">
|
||||
<td><code>@session.SessionId.Substring(0, 12)...</code></td>
|
||||
<td>@session.UserId</td>
|
||||
<td><code>@session.DeviceId.Substring(0, 12)...</code></td>
|
||||
<td>@session.CreatedAt.ToString("g")</td>
|
||||
<td>@session.ExpiresAt.ToString("g")</td>
|
||||
<td>@((int)duration.TotalHours)h</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if (Model.ExpiredSessions.Count > 20)
|
||||
{
|
||||
<div class="text-muted text-center">
|
||||
<small>Showing 20 of @Model.ExpiredSessions.Count expired sessions</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!Model.AllSessions.Any())
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> No sessions yet. Sessions will appear when players connect to the server.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
56
RR3CommunityServer/Pages/Sessions.cshtml.cs
Normal file
56
RR3CommunityServer/Pages/Sessions.cshtml.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RR3CommunityServer.Data;
|
||||
using static RR3CommunityServer.Data.RR3DbContext;
|
||||
|
||||
namespace RR3CommunityServer.Pages;
|
||||
|
||||
public class SessionsModel : PageModel
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
|
||||
public SessionsModel(RR3DbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public List<Session> AllSessions { get; set; } = new();
|
||||
public List<Session> ActiveSessions { get; set; } = new();
|
||||
public List<Session> ExpiredSessions { get; set; } = new();
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
AllSessions = await _context.Sessions
|
||||
.OrderByDescending(s => s.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
ActiveSessions = AllSessions.Where(s => s.ExpiresAt > now).ToList();
|
||||
ExpiredSessions = AllSessions.Where(s => s.ExpiresAt <= now).ToList();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteAsync(int sessionId)
|
||||
{
|
||||
var session = await _context.Sessions.FindAsync(sessionId);
|
||||
if (session != null)
|
||||
{
|
||||
_context.Sessions.Remove(session);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostCleanupExpiredAsync()
|
||||
{
|
||||
var expiredSessions = await _context.Sessions
|
||||
.Where(s => s.ExpiresAt <= DateTime.UtcNow)
|
||||
.ToListAsync();
|
||||
|
||||
_context.Sessions.RemoveRange(expiredSessions);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
213
RR3CommunityServer/Pages/Settings.cshtml
Normal file
213
RR3CommunityServer/Pages/Settings.cshtml
Normal file
@@ -0,0 +1,213 @@
|
||||
@page
|
||||
@model RR3CommunityServer.Pages.SettingsModel
|
||||
@{
|
||||
ViewData["Title"] = "Server Settings";
|
||||
}
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1>⚙️ Server Settings</h1>
|
||||
<p class="text-muted">Configure server behavior and options</p>
|
||||
</div>
|
||||
<a href="/admin" class="btn btn-outline-secondary">← Back to Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Server Configuration -->
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">🌐 Server Configuration</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4">Server URL:</dt>
|
||||
<dd class="col-sm-8">
|
||||
<code>@Model.ServerUrl</code>
|
||||
<button class="btn btn-sm btn-outline-secondary ms-2" onclick="copyToClipboard('@Model.ServerUrl')">
|
||||
<i class="bi bi-clipboard"></i> Copy
|
||||
</button>
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-4">Director Endpoint:</dt>
|
||||
<dd class="col-sm-8"><code>@Model.ServerUrl/synergy/director</code></dd>
|
||||
|
||||
<dt class="col-sm-4">Database:</dt>
|
||||
<dd class="col-sm-8">SQLite (rr3community.db)</dd>
|
||||
|
||||
<dt class="col-sm-4">Session Timeout:</dt>
|
||||
<dd class="col-sm-8">24 hours</dd>
|
||||
|
||||
<dt class="col-sm-4">Auto-Approve Purchases:</dt>
|
||||
<dd class="col-sm-8">
|
||||
<span class="badge bg-success">✓ Enabled</span>
|
||||
<small class="text-muted d-block">All purchases auto-approved for community servers</small>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- APK Configuration -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0">📱 APK Configuration</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6 class="mb-3">To connect game clients to this server:</h6>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Method 1: Use the automated script</strong>
|
||||
<pre class="mb-2 mt-2"><code>.\RR3-Community-Mod.ps1 -ServerUrl "@Model.ServerUrl"</code></pre>
|
||||
<small>Located in: <code>E:\rr3\RR3-Community-Mod.ps1</code></small>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-secondary">
|
||||
<strong>Method 2: Manual AndroidManifest.xml modification</strong>
|
||||
<ol class="small mb-0 mt-2">
|
||||
<li>Decompile APK with APKTool</li>
|
||||
<li>Edit AndroidManifest.xml:
|
||||
<ul>
|
||||
<li>Change <code>com.ea.nimble.configuration</code> from "live" to "custom"</li>
|
||||
<li>Add metadata: <code>NimbleCustomizedSynergyServerEndpointUrl</code> = <code>@Model.ServerUrl</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Recompile and sign APK</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<p class="mb-0">
|
||||
<a href="file:///E:/rr3/APK_MODIFICATION_GUIDE.md" class="btn btn-sm btn-primary">
|
||||
<i class="bi bi-book"></i> View Full Guide
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Information -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-dark text-white">
|
||||
<h5 class="mb-0">💻 System Info</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="mb-0">
|
||||
<dt>Operating System</dt>
|
||||
<dd><code>@Model.Platform</code></dd>
|
||||
|
||||
<dt>.NET Version</dt>
|
||||
<dd><code>@Model.DotNetVersion</code></dd>
|
||||
|
||||
<dt>ASP.NET Core</dt>
|
||||
<dd><code>@Model.AspNetVersion</code></dd>
|
||||
|
||||
<dt>Server Uptime</dt>
|
||||
<dd><strong>@Model.Uptime</strong></dd>
|
||||
|
||||
<dt>Process ID</dt>
|
||||
<dd><code>@Model.ProcessId</code></dd>
|
||||
|
||||
<dt>Working Memory</dt>
|
||||
<dd><code>@Model.MemoryUsage MB</code></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Links -->
|
||||
<div class="card">
|
||||
<div class="card-header bg-secondary text-white">
|
||||
<h5 class="mb-0">🔗 Quick Links</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/swagger" target="_blank" class="btn btn-outline-primary">
|
||||
<i class="bi bi-code-slash"></i> Swagger API Docs
|
||||
</a>
|
||||
<a href="file:///E:/rr3/NETWORK_COMMUNICATION_ANALYSIS.md" class="btn btn-outline-info">
|
||||
<i class="bi bi-file-text"></i> Protocol Documentation
|
||||
</a>
|
||||
<a href="file:///E:/rr3/RR3CommunityServer/README.md" class="btn btn-outline-success">
|
||||
<i class="bi bi-journal"></i> Server README
|
||||
</a>
|
||||
<a href="file:///E:/rr3/PROJECT_INDEX.md" class="btn btn-outline-warning">
|
||||
<i class="bi bi-folder"></i> Project Index
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Database Management -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h5 class="mb-0">🗄️ Database Management</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="text-primary">@Model.DbStats.Users</h3>
|
||||
<p class="mb-0 text-muted">Users</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-success">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="text-success">@Model.DbStats.Devices</h3>
|
||||
<p class="mb-0 text-muted">Devices</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-info">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="text-info">@Model.DbStats.Sessions</h3>
|
||||
<p class="mb-0 text-muted">Sessions</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-warning">
|
||||
<div class="card-body text-center">
|
||||
<h3 class="text-warning">@Model.DbStats.Purchases</h3>
|
||||
<p class="mb-0 text-muted">Purchases</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h6>⚠️ Danger Zone</h6>
|
||||
<div class="alert alert-danger">
|
||||
<form method="post" asp-page-handler="ResetDatabase" onsubmit="return confirm('This will DELETE ALL DATA and reset the database. Are you sure?')">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-exclamation-triangle"></i> Reset Database (Delete All Data)
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert('Copied to clipboard: ' + text);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
}
|
||||
74
RR3CommunityServer/Pages/Settings.cshtml.cs
Normal file
74
RR3CommunityServer/Pages/Settings.cshtml.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RR3CommunityServer.Data;
|
||||
|
||||
namespace RR3CommunityServer.Pages;
|
||||
|
||||
public class SettingsModel : PageModel
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
|
||||
public SettingsModel(RR3DbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public string ServerUrl { get; set; } = string.Empty;
|
||||
public string Platform { get; set; } = string.Empty;
|
||||
public string DotNetVersion { get; set; } = string.Empty;
|
||||
public string AspNetVersion { get; set; } = string.Empty;
|
||||
public string Uptime { get; set; } = string.Empty;
|
||||
public int ProcessId { get; set; }
|
||||
public long MemoryUsage { get; set; }
|
||||
public DatabaseStats DbStats { get; set; } = new();
|
||||
|
||||
public async Task OnGetAsync()
|
||||
{
|
||||
ServerUrl = $"{Request.Scheme}://{Request.Host}";
|
||||
Platform = Environment.OSVersion.ToString();
|
||||
DotNetVersion = Environment.Version.ToString();
|
||||
AspNetVersion = typeof(IApplicationBuilder).Assembly.GetName().Version?.ToString() ?? "Unknown";
|
||||
|
||||
var process = System.Diagnostics.Process.GetCurrentProcess();
|
||||
var uptime = DateTime.UtcNow - process.StartTime.ToUniversalTime();
|
||||
Uptime = $"{uptime.Days}d {uptime.Hours}h {uptime.Minutes}m {uptime.Seconds}s";
|
||||
ProcessId = process.Id;
|
||||
MemoryUsage = process.WorkingSet64 / 1024 / 1024; // Convert to MB
|
||||
|
||||
// Get database stats
|
||||
DbStats = new DatabaseStats
|
||||
{
|
||||
Users = await _context.Users.CountAsync(),
|
||||
Devices = await _context.Devices.CountAsync(),
|
||||
Sessions = await _context.Sessions.CountAsync(),
|
||||
Purchases = await _context.Purchases.CountAsync()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostResetDatabaseAsync()
|
||||
{
|
||||
// Delete all data
|
||||
_context.Purchases.RemoveRange(_context.Purchases);
|
||||
_context.Sessions.RemoveRange(_context.Sessions);
|
||||
_context.Users.RemoveRange(_context.Users);
|
||||
_context.Devices.RemoveRange(_context.Devices);
|
||||
_context.CatalogItems.RemoveRange(_context.CatalogItems);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Re-seed catalog
|
||||
await _context.Database.EnsureDeletedAsync();
|
||||
await _context.Database.EnsureCreatedAsync();
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
|
||||
public class DatabaseStats
|
||||
{
|
||||
public int Users { get; set; }
|
||||
public int Devices { get; set; }
|
||||
public int Sessions { get; set; }
|
||||
public int Purchases { get; set; }
|
||||
}
|
||||
111
RR3CommunityServer/Pages/Users.cshtml
Normal file
111
RR3CommunityServer/Pages/Users.cshtml
Normal file
@@ -0,0 +1,111 @@
|
||||
@page
|
||||
@model RR3CommunityServer.Pages.UsersModel
|
||||
@{
|
||||
ViewData["Title"] = "User Management";
|
||||
}
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1>👥 User Management</h1>
|
||||
<p class="text-muted">Manage all registered users</p>
|
||||
</div>
|
||||
<a href="/admin" class="btn btn-outline-secondary">← Back to Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Users.Count == 0)
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i> No users registered yet. Users will appear here when players connect to your server.
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">All Users (@Model.Users.Count)</h5>
|
||||
<form method="get" class="d-flex gap-2">
|
||||
<input type="text" name="search" value="@Model.SearchQuery" class="form-control form-control-sm" placeholder="Search by Synergy ID...">
|
||||
<button type="submit" class="btn btn-sm btn-light">Search</button>
|
||||
@if (!string.IsNullOrEmpty(Model.SearchQuery))
|
||||
{
|
||||
<a href="/admin/users" class="btn btn-sm btn-outline-light">Clear</a>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Synergy ID</th>
|
||||
<th>Device ID</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var user in Model.Users)
|
||||
{
|
||||
<tr>
|
||||
<td><strong>@user.Id</strong></td>
|
||||
<td><code>@user.SynergyId</code></td>
|
||||
<td><code>@user.DeviceId</code></td>
|
||||
<td>@user.CreatedAt.ToString("g")</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" data-bs-toggle="modal" data-bs-target="#userModal@(user.Id)">
|
||||
<i class="bi bi-eye"></i> View
|
||||
</button>
|
||||
<form method="post" asp-page-handler="Delete" class="d-inline">
|
||||
<input type="hidden" name="userId" value="@user.Id" />
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Delete this user?')">
|
||||
<i class="bi bi-trash"></i> Delete
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- User Details Modal -->
|
||||
<div class="modal fade" id="userModal@(user.Id)" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title">User Details</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-3">User ID:</dt>
|
||||
<dd class="col-sm-9">@user.Id</dd>
|
||||
|
||||
<dt class="col-sm-3">Synergy ID:</dt>
|
||||
<dd class="col-sm-9"><code>@user.SynergyId</code></dd>
|
||||
|
||||
<dt class="col-sm-3">Device ID:</dt>
|
||||
<dd class="col-sm-9"><code>@user.DeviceId</code></dd>
|
||||
|
||||
<dt class="col-sm-3">Created:</dt>
|
||||
<dd class="col-sm-9">@user.CreatedAt.ToString("F")</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
48
RR3CommunityServer/Pages/Users.cshtml.cs
Normal file
48
RR3CommunityServer/Pages/Users.cshtml.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RR3CommunityServer.Data;
|
||||
using static RR3CommunityServer.Data.RR3DbContext;
|
||||
|
||||
namespace RR3CommunityServer.Pages;
|
||||
|
||||
public class UsersModel : PageModel
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
|
||||
public UsersModel(RR3DbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public List<User> Users { get; set; } = new();
|
||||
public string? SearchQuery { get; set; }
|
||||
|
||||
public async Task OnGetAsync(string? search)
|
||||
{
|
||||
SearchQuery = search;
|
||||
|
||||
var query = _context.Users.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(search))
|
||||
{
|
||||
query = query.Where(u => u.SynergyId.Contains(search) || u.DeviceId.Contains(search));
|
||||
}
|
||||
|
||||
Users = await query
|
||||
.OrderByDescending(u => u.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostDeleteAsync(int userId)
|
||||
{
|
||||
var user = await _context.Users.FindAsync(userId);
|
||||
if (user != null)
|
||||
{
|
||||
_context.Users.Remove(user);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
}
|
||||
130
RR3CommunityServer/Pages/_Layout.cshtml
Normal file
130
RR3CommunityServer/Pages/_Layout.cshtml
Normal file
@@ -0,0 +1,130 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - RR3 Community Server</title>
|
||||
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--rr3-primary: #e63946;
|
||||
--rr3-dark: #1d3557;
|
||||
--rr3-light: #f1faee;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: linear-gradient(135deg, var(--rr3-dark) 0%, #2a4d7a 100%);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
border-radius: 12px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--rr3-primary);
|
||||
border-color: var(--rr3-primary);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #d62839;
|
||||
border-color: #d62839;
|
||||
}
|
||||
|
||||
.table-hover tbody tr:hover {
|
||||
background-color: rgba(230, 57, 70, 0.05);
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #f4f4f4;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark mb-3">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/admin">
|
||||
<span class="fs-3">🏎️</span>
|
||||
<strong>RR3 Community Server</strong>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin">
|
||||
<i class="bi bi-speedometer2"></i> Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/users">
|
||||
<i class="bi bi-people"></i> Users
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/catalog">
|
||||
<i class="bi bi-shop"></i> Catalog
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/sessions">
|
||||
<i class="bi bi-clock-history"></i> Sessions
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/purchases">
|
||||
<i class="bi bi-cart"></i> Purchases
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/swagger" target="_blank">
|
||||
<i class="bi bi-code-slash"></i> API
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main>
|
||||
@RenderBody()
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="mt-5 py-4 bg-light">
|
||||
<div class="container text-center text-muted">
|
||||
<p class="mb-0">
|
||||
<strong>RR3 Community Server</strong> - Open Source Game Server
|
||||
</p>
|
||||
<small>Made for game preservation and educational purposes</small>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap 5 JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
3
RR3CommunityServer/Pages/_ViewStart.cshtml
Normal file
3
RR3CommunityServer/Pages/_ViewStart.cshtml
Normal file
@@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
77
RR3CommunityServer/Program.cs
Normal file
77
RR3CommunityServer/Program.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RR3CommunityServer.Data;
|
||||
using RR3CommunityServer.Services;
|
||||
using RR3CommunityServer.Middleware;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddRazorPages(); // Add Razor Pages support
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
// Database
|
||||
builder.Services.AddDbContext<RR3DbContext>(options =>
|
||||
options.UseSqlite("Data Source=rr3community.db"));
|
||||
|
||||
// Custom services
|
||||
builder.Services.AddScoped<ISessionService, SessionService>();
|
||||
builder.Services.AddScoped<IUserService, UserService>();
|
||||
builder.Services.AddScoped<ICatalogService, CatalogService>();
|
||||
builder.Services.AddScoped<IDrmService, DrmService>();
|
||||
|
||||
// CORS for cross-origin requests
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
// Initialize database
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<RR3DbContext>();
|
||||
db.Database.EnsureCreated();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseCors();
|
||||
|
||||
// Custom middleware
|
||||
app.UseMiddleware<SynergyHeadersMiddleware>();
|
||||
app.UseMiddleware<SessionValidationMiddleware>();
|
||||
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.MapRazorPages(); // Add Razor Pages routing
|
||||
|
||||
// Redirect root to admin panel
|
||||
app.MapGet("/", () => Results.Redirect("/admin"));
|
||||
|
||||
Console.WriteLine("╔══════════════════════════════════════════════════════════╗");
|
||||
Console.WriteLine("║ Real Racing 3 Community Server - RUNNING ║");
|
||||
Console.WriteLine("╠══════════════════════════════════════════════════════════╣");
|
||||
Console.WriteLine("║ Server is ready to accept connections ║");
|
||||
Console.WriteLine("║ Ensure DNS/hosts file points EA servers to this IP ║");
|
||||
Console.WriteLine("╚══════════════════════════════════════════════════════════╝");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Listening on: https://localhost:5001");
|
||||
Console.WriteLine("Director endpoint: /director/api/android/getDirectionByPackage");
|
||||
Console.WriteLine();
|
||||
|
||||
app.Run();
|
||||
|
||||
41
RR3CommunityServer/Properties/launchSettings.json
Normal file
41
RR3CommunityServer/Properties/launchSettings.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:3254",
|
||||
"sslPort": 44396
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5143",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7086;http://localhost:5143",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
RR3CommunityServer/RR3CommunityServer.csproj
Normal file
20
RR3CommunityServer/RR3CommunityServer.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.24" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
RR3CommunityServer/RR3CommunityServer.http
Normal file
6
RR3CommunityServer/RR3CommunityServer.http
Normal file
@@ -0,0 +1,6 @@
|
||||
@RR3CommunityServer_HostAddress = http://localhost:5143
|
||||
|
||||
GET {{RR3CommunityServer_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
34
RR3CommunityServer/Services/IServices.cs
Normal file
34
RR3CommunityServer/Services/IServices.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace RR3CommunityServer.Services;
|
||||
|
||||
// Session Service
|
||||
public interface ISessionService
|
||||
{
|
||||
Task<string> CreateSession(string? synergyId = null);
|
||||
Task<bool> ValidateSession(string sessionId);
|
||||
Task<string?> GetSynergyIdFromSession(string sessionId);
|
||||
}
|
||||
|
||||
// User Service
|
||||
public interface IUserService
|
||||
{
|
||||
Task<string> GetOrCreateDeviceId(string? existingDeviceId, string hardwareId);
|
||||
Task<string> ValidateDeviceId(string deviceId);
|
||||
Task<string> GetOrCreateAnonUid();
|
||||
Task<string> GetOrCreateSynergyId(string deviceId);
|
||||
}
|
||||
|
||||
// Catalog Service
|
||||
public interface ICatalogService
|
||||
{
|
||||
Task<List<Models.CatalogItem>> GetAvailableItems();
|
||||
Task<List<Models.CatalogCategory>> GetCategories();
|
||||
Task<string> GetDownloadUrl(string itemId);
|
||||
}
|
||||
|
||||
// DRM Service
|
||||
public interface IDrmService
|
||||
{
|
||||
Task<string> GenerateNonce();
|
||||
Task<List<Models.PurchasedItem>> GetPurchasedItems(string synergyId);
|
||||
Task<bool> VerifyAndRecordPurchase(string synergyId, Models.PurchaseVerificationRequest request);
|
||||
}
|
||||
228
RR3CommunityServer/Services/ServiceImplementations.cs
Normal file
228
RR3CommunityServer/Services/ServiceImplementations.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RR3CommunityServer.Data;
|
||||
using RR3CommunityServer.Models;
|
||||
|
||||
namespace RR3CommunityServer.Services;
|
||||
|
||||
public class SessionService : ISessionService
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
|
||||
public SessionService(RR3DbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<string> CreateSession(string? synergyId = null)
|
||||
{
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var session = new Session
|
||||
{
|
||||
SessionId = sessionId,
|
||||
SynergyId = synergyId,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
ExpiresAt = DateTime.UtcNow.AddHours(24)
|
||||
};
|
||||
|
||||
_context.Sessions.Add(session);
|
||||
await _context.SaveChangesAsync();
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateSession(string sessionId)
|
||||
{
|
||||
var session = await _context.Sessions
|
||||
.FirstOrDefaultAsync(s => s.SessionId == sessionId);
|
||||
|
||||
return session != null && session.ExpiresAt > DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public async Task<string?> GetSynergyIdFromSession(string sessionId)
|
||||
{
|
||||
var session = await _context.Sessions
|
||||
.FirstOrDefaultAsync(s => s.SessionId == sessionId);
|
||||
|
||||
return session?.SynergyId;
|
||||
}
|
||||
}
|
||||
|
||||
public class UserService : IUserService
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
|
||||
public UserService(RR3DbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<string> GetOrCreateDeviceId(string? existingDeviceId, string hardwareId)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(existingDeviceId))
|
||||
{
|
||||
var existing = await _context.Devices
|
||||
.FirstOrDefaultAsync(d => d.DeviceId == existingDeviceId);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
existing.LastSeenAt = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
return existing.DeviceId;
|
||||
}
|
||||
}
|
||||
|
||||
var deviceId = Guid.NewGuid().ToString();
|
||||
var device = new Device
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
HardwareId = hardwareId,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
LastSeenAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.Devices.Add(device);
|
||||
await _context.SaveChangesAsync();
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public async Task<string> ValidateDeviceId(string deviceId)
|
||||
{
|
||||
var device = await _context.Devices
|
||||
.FirstOrDefaultAsync(d => d.DeviceId == deviceId);
|
||||
|
||||
if (device != null)
|
||||
{
|
||||
device.LastSeenAt = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
return "valid";
|
||||
}
|
||||
|
||||
return "invalid";
|
||||
}
|
||||
|
||||
public async Task<string> GetOrCreateAnonUid()
|
||||
{
|
||||
return await Task.FromResult(Guid.NewGuid().ToString());
|
||||
}
|
||||
|
||||
public async Task<string> GetOrCreateSynergyId(string deviceId)
|
||||
{
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.DeviceId == deviceId);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return user.SynergyId;
|
||||
}
|
||||
|
||||
var synergyId = $"SYN-{Guid.NewGuid():N}";
|
||||
user = new User
|
||||
{
|
||||
SynergyId = synergyId,
|
||||
DeviceId = deviceId,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.Users.Add(user);
|
||||
await _context.SaveChangesAsync();
|
||||
return synergyId;
|
||||
}
|
||||
}
|
||||
|
||||
public class CatalogService : ICatalogService
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
|
||||
public CatalogService(RR3DbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<List<Models.CatalogItem>> GetAvailableItems()
|
||||
{
|
||||
var items = await _context.CatalogItems
|
||||
.Where(c => c.Available)
|
||||
.ToListAsync();
|
||||
|
||||
return items.Select(e => new Models.CatalogItem
|
||||
{
|
||||
itemId = e.Sku,
|
||||
sku = e.Sku,
|
||||
name = e.Name,
|
||||
description = e.Type,
|
||||
category = e.Type,
|
||||
price = e.Price,
|
||||
currency = "USD"
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<List<CatalogCategory>> GetCategories()
|
||||
{
|
||||
var items = await _context.CatalogItems
|
||||
.Where(c => c.Available)
|
||||
.ToListAsync();
|
||||
|
||||
var categories = items.GroupBy(i => i.Type)
|
||||
.Select(g => new CatalogCategory
|
||||
{
|
||||
categoryId = g.Key,
|
||||
name = g.Key,
|
||||
itemIds = g.Select(i => i.Sku).ToList()
|
||||
}).ToList();
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
public async Task<string> GetDownloadUrl(string itemId)
|
||||
{
|
||||
return await Task.FromResult($"https://localhost:5001/downloads/{itemId}");
|
||||
}
|
||||
}
|
||||
|
||||
public class DrmService : IDrmService
|
||||
{
|
||||
private readonly RR3DbContext _context;
|
||||
|
||||
public DrmService(RR3DbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<string> GenerateNonce()
|
||||
{
|
||||
return await Task.FromResult(Convert.ToBase64String(Guid.NewGuid().ToByteArray()));
|
||||
}
|
||||
|
||||
public async Task<List<PurchasedItem>> GetPurchasedItems(string synergyId)
|
||||
{
|
||||
var purchases = await _context.Purchases
|
||||
.Where(p => p.SynergyId == synergyId)
|
||||
.ToListAsync();
|
||||
|
||||
return purchases.Select(p => new PurchasedItem
|
||||
{
|
||||
itemId = p.ItemId,
|
||||
sku = p.Sku,
|
||||
orderId = p.OrderId,
|
||||
purchaseTime = new DateTimeOffset(p.PurchaseTime).ToUnixTimeSeconds(),
|
||||
token = p.Token
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public async Task<bool> VerifyAndRecordPurchase(string synergyId, PurchaseVerificationRequest request)
|
||||
{
|
||||
// For community server, we accept all purchases
|
||||
var purchase = new Purchase
|
||||
{
|
||||
SynergyId = synergyId,
|
||||
ItemId = request.sku,
|
||||
Sku = request.sku,
|
||||
OrderId = request.orderId,
|
||||
PurchaseTime = DateTime.UtcNow,
|
||||
Token = request.receipt
|
||||
};
|
||||
|
||||
_context.Purchases.Add(purchase);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
8
RR3CommunityServer/appsettings.Development.json
Normal file
8
RR3CommunityServer/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
RR3CommunityServer/appsettings.json
Normal file
9
RR3CommunityServer/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
BIN
RR3CommunityServer/bin/Debug/net8.0/Humanizer.dll
Normal file
BIN
RR3CommunityServer/bin/Debug/net8.0/Humanizer.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
RR3CommunityServer/bin/Debug/net8.0/Microsoft.CodeAnalysis.dll
Normal file
BIN
RR3CommunityServer/bin/Debug/net8.0/Microsoft.CodeAnalysis.dll
Normal file
Binary file not shown.
BIN
RR3CommunityServer/bin/Debug/net8.0/Microsoft.Data.Sqlite.dll
Normal file
BIN
RR3CommunityServer/bin/Debug/net8.0/Microsoft.Data.Sqlite.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
RR3CommunityServer/bin/Debug/net8.0/Microsoft.OpenApi.dll
Normal file
BIN
RR3CommunityServer/bin/Debug/net8.0/Microsoft.OpenApi.dll
Normal file
Binary file not shown.
BIN
RR3CommunityServer/bin/Debug/net8.0/Mono.TextTemplating.dll
Normal file
BIN
RR3CommunityServer/bin/Debug/net8.0/Mono.TextTemplating.dll
Normal file
Binary file not shown.
945
RR3CommunityServer/bin/Debug/net8.0/RR3CommunityServer.deps.json
Normal file
945
RR3CommunityServer/bin/Debug/net8.0/RR3CommunityServer.deps.json
Normal file
@@ -0,0 +1,945 @@
|
||||
{
|
||||
"runtimeTarget": {
|
||||
"name": ".NETCoreApp,Version=v8.0",
|
||||
"signature": ""
|
||||
},
|
||||
"compilationOptions": {},
|
||||
"targets": {
|
||||
".NETCoreApp,Version=v8.0": {
|
||||
"RR3CommunityServer/1.0.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.OpenApi": "8.0.24",
|
||||
"Microsoft.EntityFrameworkCore": "8.0.11",
|
||||
"Microsoft.EntityFrameworkCore.Design": "8.0.11",
|
||||
"Microsoft.EntityFrameworkCore.Sqlite": "8.0.11",
|
||||
"Swashbuckle.AspNetCore": "6.6.2"
|
||||
},
|
||||
"runtime": {
|
||||
"RR3CommunityServer.dll": {}
|
||||
}
|
||||
},
|
||||
"Humanizer.Core/2.14.1": {
|
||||
"runtime": {
|
||||
"lib/net6.0/Humanizer.dll": {
|
||||
"assemblyVersion": "2.14.0.0",
|
||||
"fileVersion": "2.14.1.48190"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.AspNetCore.OpenApi/8.0.24": {
|
||||
"dependencies": {
|
||||
"Microsoft.OpenApi": "1.6.14"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.AspNetCore.OpenApi.dll": {
|
||||
"assemblyVersion": "8.0.24.0",
|
||||
"fileVersion": "8.0.2426.7207"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces/6.0.0": {
|
||||
"runtime": {
|
||||
"lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll": {
|
||||
"assemblyVersion": "6.0.0.0",
|
||||
"fileVersion": "6.0.21.52210"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Common/4.5.0": {
|
||||
"runtime": {
|
||||
"lib/netcoreapp3.1/Microsoft.CodeAnalysis.dll": {
|
||||
"assemblyVersion": "4.5.0.0",
|
||||
"fileVersion": "4.500.23.10905"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"lib/netcoreapp3.1/cs/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "cs"
|
||||
},
|
||||
"lib/netcoreapp3.1/de/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "de"
|
||||
},
|
||||
"lib/netcoreapp3.1/es/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "es"
|
||||
},
|
||||
"lib/netcoreapp3.1/fr/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "fr"
|
||||
},
|
||||
"lib/netcoreapp3.1/it/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "it"
|
||||
},
|
||||
"lib/netcoreapp3.1/ja/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "ja"
|
||||
},
|
||||
"lib/netcoreapp3.1/ko/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "ko"
|
||||
},
|
||||
"lib/netcoreapp3.1/pl/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "pl"
|
||||
},
|
||||
"lib/netcoreapp3.1/pt-BR/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "pt-BR"
|
||||
},
|
||||
"lib/netcoreapp3.1/ru/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "ru"
|
||||
},
|
||||
"lib/netcoreapp3.1/tr/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "tr"
|
||||
},
|
||||
"lib/netcoreapp3.1/zh-Hans/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "zh-Hans"
|
||||
},
|
||||
"lib/netcoreapp3.1/zh-Hant/Microsoft.CodeAnalysis.resources.dll": {
|
||||
"locale": "zh-Hant"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeAnalysis.CSharp/4.5.0": {
|
||||
"dependencies": {
|
||||
"Microsoft.CodeAnalysis.Common": "4.5.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netcoreapp3.1/Microsoft.CodeAnalysis.CSharp.dll": {
|
||||
"assemblyVersion": "4.5.0.0",
|
||||
"fileVersion": "4.500.23.10905"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"lib/netcoreapp3.1/cs/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "cs"
|
||||
},
|
||||
"lib/netcoreapp3.1/de/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "de"
|
||||
},
|
||||
"lib/netcoreapp3.1/es/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "es"
|
||||
},
|
||||
"lib/netcoreapp3.1/fr/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "fr"
|
||||
},
|
||||
"lib/netcoreapp3.1/it/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "it"
|
||||
},
|
||||
"lib/netcoreapp3.1/ja/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "ja"
|
||||
},
|
||||
"lib/netcoreapp3.1/ko/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "ko"
|
||||
},
|
||||
"lib/netcoreapp3.1/pl/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "pl"
|
||||
},
|
||||
"lib/netcoreapp3.1/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "pt-BR"
|
||||
},
|
||||
"lib/netcoreapp3.1/ru/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "ru"
|
||||
},
|
||||
"lib/netcoreapp3.1/tr/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "tr"
|
||||
},
|
||||
"lib/netcoreapp3.1/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "zh-Hans"
|
||||
},
|
||||
"lib/netcoreapp3.1/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||
"locale": "zh-Hant"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeAnalysis.CSharp.Workspaces/4.5.0": {
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "2.14.1",
|
||||
"Microsoft.CodeAnalysis.CSharp": "4.5.0",
|
||||
"Microsoft.CodeAnalysis.Common": "4.5.0",
|
||||
"Microsoft.CodeAnalysis.Workspaces.Common": "4.5.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netcoreapp3.1/Microsoft.CodeAnalysis.CSharp.Workspaces.dll": {
|
||||
"assemblyVersion": "4.5.0.0",
|
||||
"fileVersion": "4.500.23.10905"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"lib/netcoreapp3.1/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "cs"
|
||||
},
|
||||
"lib/netcoreapp3.1/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "de"
|
||||
},
|
||||
"lib/netcoreapp3.1/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "es"
|
||||
},
|
||||
"lib/netcoreapp3.1/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "fr"
|
||||
},
|
||||
"lib/netcoreapp3.1/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "it"
|
||||
},
|
||||
"lib/netcoreapp3.1/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "ja"
|
||||
},
|
||||
"lib/netcoreapp3.1/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "ko"
|
||||
},
|
||||
"lib/netcoreapp3.1/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "pl"
|
||||
},
|
||||
"lib/netcoreapp3.1/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "pt-BR"
|
||||
},
|
||||
"lib/netcoreapp3.1/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "ru"
|
||||
},
|
||||
"lib/netcoreapp3.1/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "tr"
|
||||
},
|
||||
"lib/netcoreapp3.1/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "zh-Hans"
|
||||
},
|
||||
"lib/netcoreapp3.1/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||
"locale": "zh-Hant"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Workspaces.Common/4.5.0": {
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "2.14.1",
|
||||
"Microsoft.Bcl.AsyncInterfaces": "6.0.0",
|
||||
"Microsoft.CodeAnalysis.Common": "4.5.0",
|
||||
"System.Composition": "6.0.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netcoreapp3.1/Microsoft.CodeAnalysis.Workspaces.dll": {
|
||||
"assemblyVersion": "4.5.0.0",
|
||||
"fileVersion": "4.500.23.10905"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"lib/netcoreapp3.1/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "cs"
|
||||
},
|
||||
"lib/netcoreapp3.1/de/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "de"
|
||||
},
|
||||
"lib/netcoreapp3.1/es/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "es"
|
||||
},
|
||||
"lib/netcoreapp3.1/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "fr"
|
||||
},
|
||||
"lib/netcoreapp3.1/it/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "it"
|
||||
},
|
||||
"lib/netcoreapp3.1/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "ja"
|
||||
},
|
||||
"lib/netcoreapp3.1/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "ko"
|
||||
},
|
||||
"lib/netcoreapp3.1/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "pl"
|
||||
},
|
||||
"lib/netcoreapp3.1/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "pt-BR"
|
||||
},
|
||||
"lib/netcoreapp3.1/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "ru"
|
||||
},
|
||||
"lib/netcoreapp3.1/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "tr"
|
||||
},
|
||||
"lib/netcoreapp3.1/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "zh-Hans"
|
||||
},
|
||||
"lib/netcoreapp3.1/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||
"locale": "zh-Hant"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Data.Sqlite.Core/8.0.11": {
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.6"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Data.Sqlite.dll": {
|
||||
"assemblyVersion": "8.0.11.0",
|
||||
"fileVersion": "8.0.1124.52104"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore/8.0.11": {
|
||||
"dependencies": {
|
||||
"Microsoft.EntityFrameworkCore.Abstractions": "8.0.11",
|
||||
"Microsoft.Extensions.Caching.Memory": "8.0.1",
|
||||
"Microsoft.Extensions.Logging": "8.0.1"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.EntityFrameworkCore.dll": {
|
||||
"assemblyVersion": "8.0.11.0",
|
||||
"fileVersion": "8.0.1124.52104"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Abstractions/8.0.11": {
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll": {
|
||||
"assemblyVersion": "8.0.11.0",
|
||||
"fileVersion": "8.0.1124.52104"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Design/8.0.11": {
|
||||
"dependencies": {
|
||||
"Humanizer.Core": "2.14.1",
|
||||
"Microsoft.CodeAnalysis.CSharp.Workspaces": "4.5.0",
|
||||
"Microsoft.EntityFrameworkCore.Relational": "8.0.11",
|
||||
"Microsoft.Extensions.DependencyModel": "8.0.2",
|
||||
"Mono.TextTemplating": "2.2.1"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.EntityFrameworkCore.Design.dll": {
|
||||
"assemblyVersion": "8.0.11.0",
|
||||
"fileVersion": "8.0.1124.52104"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Relational/8.0.11": {
|
||||
"dependencies": {
|
||||
"Microsoft.EntityFrameworkCore": "8.0.11"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll": {
|
||||
"assemblyVersion": "8.0.11.0",
|
||||
"fileVersion": "8.0.1124.52104"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Sqlite/8.0.11": {
|
||||
"dependencies": {
|
||||
"Microsoft.EntityFrameworkCore.Sqlite.Core": "8.0.11",
|
||||
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.6"
|
||||
}
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Sqlite.Core/8.0.11": {
|
||||
"dependencies": {
|
||||
"Microsoft.Data.Sqlite.Core": "8.0.11",
|
||||
"Microsoft.EntityFrameworkCore.Relational": "8.0.11",
|
||||
"Microsoft.Extensions.DependencyModel": "8.0.2"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.EntityFrameworkCore.Sqlite.dll": {
|
||||
"assemblyVersion": "8.0.11.0",
|
||||
"fileVersion": "8.0.1124.52104"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Caching.Memory/8.0.1": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2",
|
||||
"Microsoft.Extensions.Options": "8.0.2"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.Caching.Memory.dll": {
|
||||
"assemblyVersion": "8.0.0.0",
|
||||
"fileVersion": "8.0.1024.46610"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection/8.0.1": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.DependencyInjection.dll": {
|
||||
"assemblyVersion": "8.0.0.0",
|
||||
"fileVersion": "8.0.1024.46610"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.2": {
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
|
||||
"assemblyVersion": "8.0.0.0",
|
||||
"fileVersion": "8.0.1024.46610"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel/8.0.2": {
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.DependencyModel.dll": {
|
||||
"assemblyVersion": "8.0.0.2",
|
||||
"fileVersion": "8.0.1024.46610"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging/8.0.1": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection": "8.0.1",
|
||||
"Microsoft.Extensions.Logging.Abstractions": "8.0.2",
|
||||
"Microsoft.Extensions.Options": "8.0.2"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.Logging.dll": {
|
||||
"assemblyVersion": "8.0.0.0",
|
||||
"fileVersion": "8.0.1024.46610"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions/8.0.2": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.Logging.Abstractions.dll": {
|
||||
"assemblyVersion": "8.0.0.0",
|
||||
"fileVersion": "8.0.1024.46610"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.Options/8.0.2": {
|
||||
"dependencies": {
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Microsoft.Extensions.Options.dll": {
|
||||
"assemblyVersion": "8.0.0.0",
|
||||
"fileVersion": "8.0.224.6711"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Microsoft.OpenApi/1.6.14": {
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/Microsoft.OpenApi.dll": {
|
||||
"assemblyVersion": "1.6.14.0",
|
||||
"fileVersion": "1.6.14.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Mono.TextTemplating/2.2.1": {
|
||||
"dependencies": {
|
||||
"System.CodeDom": "4.4.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/Mono.TextTemplating.dll": {
|
||||
"assemblyVersion": "2.2.0.0",
|
||||
"fileVersion": "2.2.1.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.bundle_e_sqlite3/2.1.6": {
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.lib.e_sqlite3": "2.1.6",
|
||||
"SQLitePCLRaw.provider.e_sqlite3": "2.1.6"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {
|
||||
"assemblyVersion": "2.1.6.2060",
|
||||
"fileVersion": "2.1.6.2060"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.core/2.1.6": {
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/SQLitePCLRaw.core.dll": {
|
||||
"assemblyVersion": "2.1.6.2060",
|
||||
"fileVersion": "2.1.6.2060"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.lib.e_sqlite3/2.1.6": {
|
||||
"runtimeTargets": {
|
||||
"runtimes/browser-wasm/nativeassets/net8.0/e_sqlite3.a": {
|
||||
"rid": "browser-wasm",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/linux-arm/native/libe_sqlite3.so": {
|
||||
"rid": "linux-arm",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/linux-arm64/native/libe_sqlite3.so": {
|
||||
"rid": "linux-arm64",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/linux-armel/native/libe_sqlite3.so": {
|
||||
"rid": "linux-armel",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/linux-mips64/native/libe_sqlite3.so": {
|
||||
"rid": "linux-mips64",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/linux-musl-arm/native/libe_sqlite3.so": {
|
||||
"rid": "linux-musl-arm",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/linux-musl-arm64/native/libe_sqlite3.so": {
|
||||
"rid": "linux-musl-arm64",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/linux-musl-x64/native/libe_sqlite3.so": {
|
||||
"rid": "linux-musl-x64",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/linux-ppc64le/native/libe_sqlite3.so": {
|
||||
"rid": "linux-ppc64le",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/linux-s390x/native/libe_sqlite3.so": {
|
||||
"rid": "linux-s390x",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/linux-x64/native/libe_sqlite3.so": {
|
||||
"rid": "linux-x64",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/linux-x86/native/libe_sqlite3.so": {
|
||||
"rid": "linux-x86",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/maccatalyst-arm64/native/libe_sqlite3.dylib": {
|
||||
"rid": "maccatalyst-arm64",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/maccatalyst-x64/native/libe_sqlite3.dylib": {
|
||||
"rid": "maccatalyst-x64",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/osx-arm64/native/libe_sqlite3.dylib": {
|
||||
"rid": "osx-arm64",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/osx-x64/native/libe_sqlite3.dylib": {
|
||||
"rid": "osx-x64",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/win-arm/native/e_sqlite3.dll": {
|
||||
"rid": "win-arm",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/win-arm64/native/e_sqlite3.dll": {
|
||||
"rid": "win-arm64",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/win-x64/native/e_sqlite3.dll": {
|
||||
"rid": "win-x64",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
},
|
||||
"runtimes/win-x86/native/e_sqlite3.dll": {
|
||||
"rid": "win-x86",
|
||||
"assetType": "native",
|
||||
"fileVersion": "0.0.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SQLitePCLRaw.provider.e_sqlite3/2.1.6": {
|
||||
"dependencies": {
|
||||
"SQLitePCLRaw.core": "2.1.6"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/SQLitePCLRaw.provider.e_sqlite3.dll": {
|
||||
"assemblyVersion": "2.1.6.2060",
|
||||
"fileVersion": "2.1.6.2060"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Swashbuckle.AspNetCore/6.6.2": {
|
||||
"dependencies": {
|
||||
"Swashbuckle.AspNetCore.Swagger": "6.6.2",
|
||||
"Swashbuckle.AspNetCore.SwaggerGen": "6.6.2",
|
||||
"Swashbuckle.AspNetCore.SwaggerUI": "6.6.2"
|
||||
}
|
||||
},
|
||||
"Swashbuckle.AspNetCore.Swagger/6.6.2": {
|
||||
"dependencies": {
|
||||
"Microsoft.OpenApi": "1.6.14"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Swashbuckle.AspNetCore.Swagger.dll": {
|
||||
"assemblyVersion": "6.6.2.0",
|
||||
"fileVersion": "6.6.2.401"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Swashbuckle.AspNetCore.SwaggerGen/6.6.2": {
|
||||
"dependencies": {
|
||||
"Swashbuckle.AspNetCore.Swagger": "6.6.2"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerGen.dll": {
|
||||
"assemblyVersion": "6.6.2.0",
|
||||
"fileVersion": "6.6.2.401"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Swashbuckle.AspNetCore.SwaggerUI/6.6.2": {
|
||||
"runtime": {
|
||||
"lib/net8.0/Swashbuckle.AspNetCore.SwaggerUI.dll": {
|
||||
"assemblyVersion": "6.6.2.0",
|
||||
"fileVersion": "6.6.2.401"
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.CodeDom/4.4.0": {
|
||||
"runtime": {
|
||||
"lib/netstandard2.0/System.CodeDom.dll": {
|
||||
"assemblyVersion": "4.0.0.0",
|
||||
"fileVersion": "4.6.25519.3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.Composition/6.0.0": {
|
||||
"dependencies": {
|
||||
"System.Composition.AttributedModel": "6.0.0",
|
||||
"System.Composition.Convention": "6.0.0",
|
||||
"System.Composition.Hosting": "6.0.0",
|
||||
"System.Composition.Runtime": "6.0.0",
|
||||
"System.Composition.TypedParts": "6.0.0"
|
||||
}
|
||||
},
|
||||
"System.Composition.AttributedModel/6.0.0": {
|
||||
"runtime": {
|
||||
"lib/net6.0/System.Composition.AttributedModel.dll": {
|
||||
"assemblyVersion": "6.0.0.0",
|
||||
"fileVersion": "6.0.21.52210"
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.Composition.Convention/6.0.0": {
|
||||
"dependencies": {
|
||||
"System.Composition.AttributedModel": "6.0.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/System.Composition.Convention.dll": {
|
||||
"assemblyVersion": "6.0.0.0",
|
||||
"fileVersion": "6.0.21.52210"
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.Composition.Hosting/6.0.0": {
|
||||
"dependencies": {
|
||||
"System.Composition.Runtime": "6.0.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/System.Composition.Hosting.dll": {
|
||||
"assemblyVersion": "6.0.0.0",
|
||||
"fileVersion": "6.0.21.52210"
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.Composition.Runtime/6.0.0": {
|
||||
"runtime": {
|
||||
"lib/net6.0/System.Composition.Runtime.dll": {
|
||||
"assemblyVersion": "6.0.0.0",
|
||||
"fileVersion": "6.0.21.52210"
|
||||
}
|
||||
}
|
||||
},
|
||||
"System.Composition.TypedParts/6.0.0": {
|
||||
"dependencies": {
|
||||
"System.Composition.AttributedModel": "6.0.0",
|
||||
"System.Composition.Hosting": "6.0.0",
|
||||
"System.Composition.Runtime": "6.0.0"
|
||||
},
|
||||
"runtime": {
|
||||
"lib/net6.0/System.Composition.TypedParts.dll": {
|
||||
"assemblyVersion": "6.0.0.0",
|
||||
"fileVersion": "6.0.21.52210"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"RR3CommunityServer/1.0.0": {
|
||||
"type": "project",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
},
|
||||
"Humanizer.Core/2.14.1": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==",
|
||||
"path": "humanizer.core/2.14.1",
|
||||
"hashPath": "humanizer.core.2.14.1.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.AspNetCore.OpenApi/8.0.24": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-rqHY6POxy1e0vf7opG5hsxR0+Z0svcMYDvaEQW+T93/YeyFlaFOqQkZ6t1C8SaNLyH6LFlSnOXQ1Jf9Q+JFEhg==",
|
||||
"path": "microsoft.aspnetcore.openapi/8.0.24",
|
||||
"hashPath": "microsoft.aspnetcore.openapi.8.0.24.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Bcl.AsyncInterfaces/6.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==",
|
||||
"path": "microsoft.bcl.asyncinterfaces/6.0.0",
|
||||
"hashPath": "microsoft.bcl.asyncinterfaces.6.0.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Common/4.5.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-lwAbIZNdnY0SUNoDmZHkVUwLO8UyNnyyh1t/4XsbFxi4Ounb3xszIYZaWhyj5ZjyfcwqwmtMbE7fUTVCqQEIdQ==",
|
||||
"path": "microsoft.codeanalysis.common/4.5.0",
|
||||
"hashPath": "microsoft.codeanalysis.common.4.5.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.CodeAnalysis.CSharp/4.5.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-cM59oMKAOxvdv76bdmaKPy5hfj+oR+zxikWoueEB7CwTko7mt9sVKZI8Qxlov0C/LuKEG+WQwifepqL3vuTiBQ==",
|
||||
"path": "microsoft.codeanalysis.csharp/4.5.0",
|
||||
"hashPath": "microsoft.codeanalysis.csharp.4.5.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.CodeAnalysis.CSharp.Workspaces/4.5.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-h74wTpmGOp4yS4hj+EvNzEiPgg/KVs2wmSfTZ81upJZOtPkJsVkgfsgtxxqmAeapjT/vLKfmYV0bS8n5MNVP+g==",
|
||||
"path": "microsoft.codeanalysis.csharp.workspaces/4.5.0",
|
||||
"hashPath": "microsoft.codeanalysis.csharp.workspaces.4.5.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Workspaces.Common/4.5.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-l4dDRmGELXG72XZaonnOeORyD/T5RpEu5LGHOUIhnv+MmUWDY/m1kWXGwtcgQ5CJ5ynkFiRnIYzTKXYjUs7rbw==",
|
||||
"path": "microsoft.codeanalysis.workspaces.common/4.5.0",
|
||||
"hashPath": "microsoft.codeanalysis.workspaces.common.4.5.0.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Data.Sqlite.Core/8.0.11": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-PrDkI9SeU/MEP/IHriczeYmRVbzEcfp66UlZRjL5ikHIJGIYOrby55GoehLCJzJiTwJ+rGkjSRctZnWgfC95fg==",
|
||||
"path": "microsoft.data.sqlite.core/8.0.11",
|
||||
"hashPath": "microsoft.data.sqlite.core.8.0.11.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore/8.0.11": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-stbjWBTtpQ1HtqXMFyKnXFTr76PvaOHI2b2h85JqBi3eZr00nspvR/a90Zwh8CQ4rVawqLiTG0+0yZQWaav+sQ==",
|
||||
"path": "microsoft.entityframeworkcore/8.0.11",
|
||||
"hashPath": "microsoft.entityframeworkcore.8.0.11.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Abstractions/8.0.11": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-++zY0Ea724ku1jptWJmF7jm3I4IXTexfT4qi1ETcSFFF7qj+qm6rRgN7mTuKkwIETuXk0ikfzudryRjUGrrNKQ==",
|
||||
"path": "microsoft.entityframeworkcore.abstractions/8.0.11",
|
||||
"hashPath": "microsoft.entityframeworkcore.abstractions.8.0.11.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Design/8.0.11": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-KxOvpbaKiUmbLvenr0T/4F1Vdm0Sq+iajLbesQK7/WKB/Dx+FQHCZ0f5jCXrVWK2QKF9eHzQ5JPA1L6hcb25FQ==",
|
||||
"path": "microsoft.entityframeworkcore.design/8.0.11",
|
||||
"hashPath": "microsoft.entityframeworkcore.design.8.0.11.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Relational/8.0.11": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-3TuuW3i5I4Ro0yoaHmi2MqEDGObOVuhLaMEnd/heaLB1fcvm4fu4PevmC4BOWnI0vo176AIlV5o4rEQciLoohw==",
|
||||
"path": "microsoft.entityframeworkcore.relational/8.0.11",
|
||||
"hashPath": "microsoft.entityframeworkcore.relational.8.0.11.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Sqlite/8.0.11": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-HJN+xx8lomTIq7SpshnUzHt7uo1/AOvnPWjXsOzyCsoYMEpfRKjxsJobcHu8Qpvd2mwzZB/mzjPUE8XeuGiCGA==",
|
||||
"path": "microsoft.entityframeworkcore.sqlite/8.0.11",
|
||||
"hashPath": "microsoft.entityframeworkcore.sqlite.8.0.11.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.EntityFrameworkCore.Sqlite.Core/8.0.11": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-wvC/xpis//IG9qvfMbMFMjhrM+P7choZ23CHBRfQyfmIkOVZLBtzM6nestbDdAv3eGnJym1/m0o0sc7YXlL0yg==",
|
||||
"path": "microsoft.entityframeworkcore.sqlite.core/8.0.11",
|
||||
"hashPath": "microsoft.entityframeworkcore.sqlite.core.8.0.11.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.Caching.Memory/8.0.1": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-HFDnhYLccngrzyGgHkjEDU5FMLn4MpOsr5ElgsBMC4yx6lJh4jeWO7fHS8+TXPq+dgxCmUa/Trl8svObmwW4QA==",
|
||||
"path": "microsoft.extensions.caching.memory/8.0.1",
|
||||
"hashPath": "microsoft.extensions.caching.memory.8.0.1.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection/8.0.1": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-BmANAnR5Xd4Oqw7yQ75xOAYODybZQRzdeNucg7kS5wWKd2PNnMdYtJ2Vciy0QLylRmv42DGl5+AFL9izA6F1Rw==",
|
||||
"path": "microsoft.extensions.dependencyinjection/8.0.1",
|
||||
"hashPath": "microsoft.extensions.dependencyinjection.8.0.1.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions/8.0.2": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==",
|
||||
"path": "microsoft.extensions.dependencyinjection.abstractions/8.0.2",
|
||||
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.8.0.2.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.DependencyModel/8.0.2": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-mUBDZZRgZrSyFOsJ2qJJ9fXfqd/kXJwf3AiDoqLD9m6TjY5OO/vLNOb9fb4juC0487eq4hcGN/M2Rh/CKS7QYw==",
|
||||
"path": "microsoft.extensions.dependencymodel/8.0.2",
|
||||
"hashPath": "microsoft.extensions.dependencymodel.8.0.2.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.Logging/8.0.1": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-4x+pzsQEbqxhNf1QYRr5TDkLP9UsLT3A6MdRKDDEgrW7h1ljiEPgTNhKYUhNCCAaVpQECVQ+onA91PTPnIp6Lw==",
|
||||
"path": "microsoft.extensions.logging/8.0.1",
|
||||
"hashPath": "microsoft.extensions.logging.8.0.1.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions/8.0.2": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-nroMDjS7hNBPtkZqVBbSiQaQjWRDxITI8Y7XnDs97rqG3EbzVTNLZQf7bIeUJcaHOV8bca47s1Uxq94+2oGdxA==",
|
||||
"path": "microsoft.extensions.logging.abstractions/8.0.2",
|
||||
"hashPath": "microsoft.extensions.logging.abstractions.8.0.2.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.Extensions.Options/8.0.2": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-dWGKvhFybsaZpGmzkGCbNNwBD1rVlWzrZKANLW/CcbFJpCEceMCGzT7zZwHOGBCbwM0SzBuceMj5HN1LKV1QqA==",
|
||||
"path": "microsoft.extensions.options/8.0.2",
|
||||
"hashPath": "microsoft.extensions.options.8.0.2.nupkg.sha512"
|
||||
},
|
||||
"Microsoft.OpenApi/1.6.14": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-tTaBT8qjk3xINfESyOPE2rIellPvB7qpVqiWiyA/lACVvz+xOGiXhFUfohcx82NLbi5avzLW0lx+s6oAqQijfw==",
|
||||
"path": "microsoft.openapi/1.6.14",
|
||||
"hashPath": "microsoft.openapi.1.6.14.nupkg.sha512"
|
||||
},
|
||||
"Mono.TextTemplating/2.2.1": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-KZYeKBET/2Z0gY1WlTAK7+RHTl7GSbtvTLDXEZZojUdAPqpQNDL6tHv7VUpqfX5VEOh+uRGKaZXkuD253nEOBQ==",
|
||||
"path": "mono.texttemplating/2.2.1",
|
||||
"hashPath": "mono.texttemplating.2.2.1.nupkg.sha512"
|
||||
},
|
||||
"SQLitePCLRaw.bundle_e_sqlite3/2.1.6": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-BmAf6XWt4TqtowmiWe4/5rRot6GerAeklmOPfviOvwLoF5WwgxcJHAxZtySuyW9r9w+HLILnm8VfJFLCUJYW8A==",
|
||||
"path": "sqlitepclraw.bundle_e_sqlite3/2.1.6",
|
||||
"hashPath": "sqlitepclraw.bundle_e_sqlite3.2.1.6.nupkg.sha512"
|
||||
},
|
||||
"SQLitePCLRaw.core/2.1.6": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-wO6v9GeMx9CUngAet8hbO7xdm+M42p1XeJq47ogyRoYSvNSp0NGLI+MgC0bhrMk9C17MTVFlLiN6ylyExLCc5w==",
|
||||
"path": "sqlitepclraw.core/2.1.6",
|
||||
"hashPath": "sqlitepclraw.core.2.1.6.nupkg.sha512"
|
||||
},
|
||||
"SQLitePCLRaw.lib.e_sqlite3/2.1.6": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-2ObJJLkIUIxRpOUlZNGuD4rICpBnrBR5anjyfUFQep4hMOIeqW+XGQYzrNmHSVz5xSWZ3klSbh7sFR6UyDj68Q==",
|
||||
"path": "sqlitepclraw.lib.e_sqlite3/2.1.6",
|
||||
"hashPath": "sqlitepclraw.lib.e_sqlite3.2.1.6.nupkg.sha512"
|
||||
},
|
||||
"SQLitePCLRaw.provider.e_sqlite3/2.1.6": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-PQ2Oq3yepLY4P7ll145P3xtx2bX8xF4PzaKPRpw9jZlKvfe4LE/saAV82inND9usn1XRpmxXk7Lal3MTI+6CNg==",
|
||||
"path": "sqlitepclraw.provider.e_sqlite3/2.1.6",
|
||||
"hashPath": "sqlitepclraw.provider.e_sqlite3.2.1.6.nupkg.sha512"
|
||||
},
|
||||
"Swashbuckle.AspNetCore/6.6.2": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-+NB4UYVYN6AhDSjW0IJAd1AGD8V33gemFNLPaxKTtPkHB+HaKAKf9MGAEUPivEWvqeQfcKIw8lJaHq6LHljRuw==",
|
||||
"path": "swashbuckle.aspnetcore/6.6.2",
|
||||
"hashPath": "swashbuckle.aspnetcore.6.6.2.nupkg.sha512"
|
||||
},
|
||||
"Swashbuckle.AspNetCore.Swagger/6.6.2": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-ovgPTSYX83UrQUWiS5vzDcJ8TEX1MAxBgDFMK45rC24MorHEPQlZAHlaXj/yth4Zf6xcktpUgTEBvffRQVwDKA==",
|
||||
"path": "swashbuckle.aspnetcore.swagger/6.6.2",
|
||||
"hashPath": "swashbuckle.aspnetcore.swagger.6.6.2.nupkg.sha512"
|
||||
},
|
||||
"Swashbuckle.AspNetCore.SwaggerGen/6.6.2": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-zv4ikn4AT1VYuOsDCpktLq4QDq08e7Utzbir86M5/ZkRaLXbCPF11E1/vTmOiDzRTl0zTZINQU2qLKwTcHgfrA==",
|
||||
"path": "swashbuckle.aspnetcore.swaggergen/6.6.2",
|
||||
"hashPath": "swashbuckle.aspnetcore.swaggergen.6.6.2.nupkg.sha512"
|
||||
},
|
||||
"Swashbuckle.AspNetCore.SwaggerUI/6.6.2": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-mBBb+/8Hm2Q3Wygag+hu2jj69tZW5psuv0vMRXY07Wy+Rrj40vRP8ZTbKBhs91r45/HXT4aY4z0iSBYx1h6JvA==",
|
||||
"path": "swashbuckle.aspnetcore.swaggerui/6.6.2",
|
||||
"hashPath": "swashbuckle.aspnetcore.swaggerui.6.6.2.nupkg.sha512"
|
||||
},
|
||||
"System.CodeDom/4.4.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-2sCCb7doXEwtYAbqzbF/8UAeDRMNmPaQbU2q50Psg1J9KzumyVVCgKQY8s53WIPTufNT0DpSe9QRvVjOzfDWBA==",
|
||||
"path": "system.codedom/4.4.0",
|
||||
"hashPath": "system.codedom.4.4.0.nupkg.sha512"
|
||||
},
|
||||
"System.Composition/6.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-d7wMuKQtfsxUa7S13tITC8n1cQzewuhD5iDjZtK2prwFfKVzdYtgrTHgjaV03Zq7feGQ5gkP85tJJntXwInsJA==",
|
||||
"path": "system.composition/6.0.0",
|
||||
"hashPath": "system.composition.6.0.0.nupkg.sha512"
|
||||
},
|
||||
"System.Composition.AttributedModel/6.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-WK1nSDLByK/4VoC7fkNiFuTVEiperuCN/Hyn+VN30R+W2ijO1d0Z2Qm0ScEl9xkSn1G2MyapJi8xpf4R8WRa/w==",
|
||||
"path": "system.composition.attributedmodel/6.0.0",
|
||||
"hashPath": "system.composition.attributedmodel.6.0.0.nupkg.sha512"
|
||||
},
|
||||
"System.Composition.Convention/6.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-XYi4lPRdu5bM4JVJ3/UIHAiG6V6lWWUlkhB9ab4IOq0FrRsp0F4wTyV4Dj+Ds+efoXJ3qbLqlvaUozDO7OLeXA==",
|
||||
"path": "system.composition.convention/6.0.0",
|
||||
"hashPath": "system.composition.convention.6.0.0.nupkg.sha512"
|
||||
},
|
||||
"System.Composition.Hosting/6.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-w/wXjj7kvxuHPLdzZ0PAUt++qJl03t7lENmb2Oev0n3zbxyNULbWBlnd5J5WUMMv15kg5o+/TCZFb6lSwfaUUQ==",
|
||||
"path": "system.composition.hosting/6.0.0",
|
||||
"hashPath": "system.composition.hosting.6.0.0.nupkg.sha512"
|
||||
},
|
||||
"System.Composition.Runtime/6.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-qkRH/YBaMPTnzxrS5RDk1juvqed4A6HOD/CwRcDGyPpYps1J27waBddiiq1y93jk2ZZ9wuA/kynM+NO0kb3PKg==",
|
||||
"path": "system.composition.runtime/6.0.0",
|
||||
"hashPath": "system.composition.runtime.6.0.0.nupkg.sha512"
|
||||
},
|
||||
"System.Composition.TypedParts/6.0.0": {
|
||||
"type": "package",
|
||||
"serviceable": true,
|
||||
"sha512": "sha512-iUR1eHrL8Cwd82neQCJ00MpwNIBs4NZgXzrPqx8NJf/k4+mwBO0XCRmHYJT4OLSwDDqh5nBLJWkz5cROnrGhRA==",
|
||||
"path": "system.composition.typedparts/6.0.0",
|
||||
"hashPath": "system.composition.typedparts.6.0.0.nupkg.sha512"
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
RR3CommunityServer/bin/Debug/net8.0/RR3CommunityServer.dll
Normal file
BIN
RR3CommunityServer/bin/Debug/net8.0/RR3CommunityServer.dll
Normal file
Binary file not shown.
BIN
RR3CommunityServer/bin/Debug/net8.0/RR3CommunityServer.exe
Normal file
BIN
RR3CommunityServer/bin/Debug/net8.0/RR3CommunityServer.exe
Normal file
Binary file not shown.
BIN
RR3CommunityServer/bin/Debug/net8.0/RR3CommunityServer.pdb
Normal file
BIN
RR3CommunityServer/bin/Debug/net8.0/RR3CommunityServer.pdb
Normal file
Binary file not shown.
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net8.0",
|
||||
"frameworks": [
|
||||
{
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "8.0.0"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.AspNetCore.App",
|
||||
"version": "8.0.0"
|
||||
}
|
||||
],
|
||||
"configProperties": {
|
||||
"System.GC.Server": true,
|
||||
"System.Reflection.NullabilityInfoContext.IsSupported": true,
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"Version":1,"ManifestType":"Build","Endpoints":[]}
|
||||
@@ -0,0 +1 @@
|
||||
{"ContentRoots":["E:\\rr3\\RR3CommunityServer\\RR3CommunityServer\\wwwroot\\"],"Root":{"Children":null,"Asset":null,"Patterns":[{"ContentRootIndex":0,"Pattern":"**","Depth":0}]}}
|
||||
Binary file not shown.
BIN
RR3CommunityServer/bin/Debug/net8.0/SQLitePCLRaw.core.dll
Normal file
BIN
RR3CommunityServer/bin/Debug/net8.0/SQLitePCLRaw.core.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
RR3CommunityServer/bin/Debug/net8.0/System.CodeDom.dll
Normal file
BIN
RR3CommunityServer/bin/Debug/net8.0/System.CodeDom.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
RR3CommunityServer/bin/Debug/net8.0/appsettings.json
Normal file
9
RR3CommunityServer/bin/Debug/net8.0/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user