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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user