using System; using System.Net; using System.Text.Json; using Expedience.Api.Encryption; using Expedience.Infrastructure; using Expedience.Infrastructure.Concurrency; using Expedience.Infrastructure.Models; using MassTransit; using Microsoft.AspNetCore.Mvc; namespace Expedience.Api.Controllers { [Route("api/[controller]")] [ApiController] public class ExpedienceController : ControllerBase { private readonly ILogger _logger; private readonly IPublishEndpoint _publisher; private readonly IDecryptor _decrypytor; private readonly IDistributedLock _distributedLock; private readonly IServiceScopeFactory _serviceScopeFactory; public ExpedienceController(ILogger logger, IPublishEndpoint publisher, IDecryptor decrypytor, IDistributedLock distributedLock, IServiceScopeFactory serviceScopeFactory) { _logger = logger; _publisher = publisher; _decrypytor = decrypytor; _distributedLock = distributedLock; _serviceScopeFactory = serviceScopeFactory; } [HttpPost("DutyCompletion")] public async Task PostDutyCompletionResult(List encryptedPayloads) { var utcNow = DateTime.UtcNow; try { foreach (var payload in encryptedPayloads) { var decryptedData = _decrypytor.Decrypt(payload); var completionResult = JsonSerializer.Deserialize(decryptedData)!; completionResult.UploadDateUtc = utcNow; await _publisher.Publish(completionResult); } } catch (Exception ex) { _logger.LogError(ex, "Error publishing completion result: {errorMessage}", ex.Message); } return Ok(); } [HttpGet("UserName/{worldId}/{userHash}")] public async Task GetUserName(int worldId, string userHash, CancellationToken cancellationToken) { using var scope = _serviceScopeFactory.CreateScope(); using var dbContext = scope.ServiceProvider.GetRequiredService(); var lockKey = $"{worldId}-{userHash}"; if (_distributedLock.AcquireLock(lockKey, TimeSpan.FromSeconds(10), TimeSpan.FromMinutes(4))) { try { var user = dbContext.Users.FirstOrDefault(x => x.UserHash == userHash && x.WorldId == worldId); if (user == null) { string userName; var isDuplicate = false; do { userName = UserNameGenerator.Generate(); isDuplicate = dbContext.Users.Any(x => x.WorldId == worldId && x.UserName == userName); await Task.Delay(20, cancellationToken); // Don't hog the CPU } while (isDuplicate == true); user = new User { UserHash = userHash, WorldId = worldId, UserName = userName, CreatedAt = DateTime.UtcNow, }; dbContext.Users.Add(user); await dbContext.SaveChangesAsync(cancellationToken); } return Ok(user.UserName); } catch (Exception ex) { _logger.LogError(ex, "Error obtaining user name for World Id {worldId} and hash {userHash}: {errorMessage}", worldId, userHash, ex.Message); } finally { _distributedLock.ReleaseLock(lockKey); } } else { _logger.LogError("Could not acquire lock for {lockKey}", lockKey); } return StatusCode(500); } } }