- 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>
229 lines
6.0 KiB
C#
229 lines
6.0 KiB
C#
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;
|
|
}
|
|
}
|