Kill switch
Deploy Promiscuity Auth API / deploy (push) Successful in 47s
Deploy Promiscuity Crafting API / deploy (push) Has been cancelled
Deploy Promiscuity Inventory API / deploy (push) Has been cancelled
Deploy Promiscuity Locations API / deploy (push) Has been cancelled
Deploy Promiscuity Mail API / deploy (push) Has been cancelled
Deploy Promiscuity World API / deploy (push) Has been cancelled
Deploy Promiscuity Character API / deploy (push) Has been cancelled
k8s smoke test / test (push) Has been cancelled
Deploy Promiscuity Auth API / deploy (push) Successful in 47s
Deploy Promiscuity Crafting API / deploy (push) Has been cancelled
Deploy Promiscuity Inventory API / deploy (push) Has been cancelled
Deploy Promiscuity Locations API / deploy (push) Has been cancelled
Deploy Promiscuity Mail API / deploy (push) Has been cancelled
Deploy Promiscuity World API / deploy (push) Has been cancelled
Deploy Promiscuity Character API / deploy (push) Has been cancelled
k8s smoke test / test (push) Has been cancelled
This commit is contained in:
@@ -14,6 +14,8 @@ namespace LocationsApi.Controllers;
|
||||
[Route("api/[controller]")]
|
||||
public class LocationsController : ControllerBase
|
||||
{
|
||||
private const string ResetWorldConfirmationPhrase = "RESET WORLD TO ORIGIN";
|
||||
private static readonly Coord OriginCoord = new() { X = 0, Y = 0 };
|
||||
private readonly LocationStore _locations;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IConfiguration _configuration;
|
||||
@@ -131,6 +133,63 @@ public class LocationsController : ControllerBase
|
||||
return Ok(locations);
|
||||
}
|
||||
|
||||
[HttpPost("reset-world")]
|
||||
[Authorize(Roles = "SUPER")]
|
||||
public async Task<IActionResult> ResetWorld([FromBody] ResetWorldRequest req)
|
||||
{
|
||||
if (!req.ConfirmDeleteAllNonOriginLocations)
|
||||
return BadRequest("confirmDeleteAllNonOriginLocations must be true");
|
||||
if (!string.Equals(req.ConfirmationPhrase?.Trim(), ResetWorldConfirmationPhrase, StringComparison.Ordinal))
|
||||
return BadRequest($"confirmationPhrase must exactly match '{ResetWorldConfirmationPhrase}'");
|
||||
|
||||
try
|
||||
{
|
||||
var limbo = await _locations.EnsureLimboLocationAsync();
|
||||
if (string.IsNullOrWhiteSpace(limbo.Id))
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, "Limbo location was created without an id");
|
||||
|
||||
var movedCharacterCount = await ResetCharactersToOriginAsync();
|
||||
var locationIdsToReassign = await _locations.GetLocationIdsForWorldResetAsync(limbo.Id);
|
||||
var reassignedInventoryItemCount = await ReassignLocationInventoryToLimboAsync(locationIdsToReassign, limbo.Id);
|
||||
var result = await _locations.ResetWorldToOriginAsync(limbo.Id);
|
||||
result.MovedCharacterCount = movedCharacterCount;
|
||||
result.ReassignedInventoryItemCount = reassignedInventoryItemCount;
|
||||
_logger.LogWarning(
|
||||
"World reset to origin by user {UserId}. DeletedLocationCount={DeletedLocationCount} MovedCharacterCount={MovedCharacterCount} ReassignedInventoryItemCount={ReassignedInventoryItemCount}",
|
||||
User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "unknown",
|
||||
result.DeletedLocationCount,
|
||||
result.MovedCharacterCount,
|
||||
result.ReassignedInventoryItemCount);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "World reset failed because biome definitions are unavailable.");
|
||||
return Conflict("Cannot reset world because no biome definitions exist.");
|
||||
}
|
||||
catch (InternalServiceCallException ex)
|
||||
{
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"World reset failed while calling {ServiceName}. StatusCode={StatusCode}",
|
||||
ex.ServiceName,
|
||||
ex.StatusCode);
|
||||
return StatusCode(ex.StatusCode, ex.ResponseBody);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "World reset failed while reaching a dependent internal service.");
|
||||
return StatusCode(StatusCodes.Status502BadGateway, new
|
||||
{
|
||||
type = "https://httpstatuses.com/502",
|
||||
title = "Bad Gateway",
|
||||
status = 502,
|
||||
detail = "Failed to reach a dependent internal service during world reset.",
|
||||
traceId = HttpContext.TraceIdentifier
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(Roles = "SUPER")]
|
||||
public async Task<IActionResult> Delete(string id)
|
||||
@@ -435,4 +494,83 @@ public class LocationsController : ControllerBase
|
||||
|
||||
public List<FloorInventoryItemResponse> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
private async Task<int> ResetCharactersToOriginAsync()
|
||||
{
|
||||
var characterBaseUrl = (_configuration["Services:CharacterApiBaseUrl"] ?? "http://localhost:50785").TrimEnd('/');
|
||||
var internalApiKey = (_configuration["InternalApi:Key"] ?? _configuration["Jwt:Key"] ?? string.Empty).Trim();
|
||||
var body = JsonSerializer.Serialize(new { coord = OriginCoord });
|
||||
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, $"{characterBaseUrl}/api/characters/internal/reset-coords");
|
||||
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||
request.Headers.Add("X-Internal-Api-Key", internalApiKey);
|
||||
|
||||
using var response = await client.SendAsync(request);
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
if (!response.IsSuccessStatusCode)
|
||||
throw new InternalServiceCallException("CharacterApi", (int)response.StatusCode, responseBody);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(responseBody))
|
||||
return 0;
|
||||
|
||||
using var json = JsonDocument.Parse(responseBody);
|
||||
return json.RootElement.TryGetProperty("updatedCharacterCount", out var countElement) && countElement.ValueKind == JsonValueKind.Number
|
||||
? countElement.GetInt32()
|
||||
: 0;
|
||||
}
|
||||
|
||||
private async Task<int> ReassignLocationInventoryToLimboAsync(IEnumerable<string> fromOwnerIds, string toOwnerId)
|
||||
{
|
||||
var ownerIds = fromOwnerIds
|
||||
.Where(id => !string.IsNullOrWhiteSpace(id))
|
||||
.Select(id => id.Trim())
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToList();
|
||||
if (ownerIds.Count == 0)
|
||||
return 0;
|
||||
|
||||
var inventoryBaseUrl = (_configuration["Services:InventoryApiBaseUrl"] ?? "http://localhost:5003").TrimEnd('/');
|
||||
var internalApiKey = (_configuration["InternalApi:Key"] ?? _configuration["Jwt:Key"] ?? string.Empty).Trim();
|
||||
var body = JsonSerializer.Serialize(new
|
||||
{
|
||||
fromOwnerIds = ownerIds,
|
||||
toOwnerId
|
||||
});
|
||||
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, $"{inventoryBaseUrl}/api/inventory/internal/location-owner/reassign");
|
||||
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||
request.Headers.Add("X-Internal-Api-Key", internalApiKey);
|
||||
|
||||
using var response = await client.SendAsync(request);
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
if (!response.IsSuccessStatusCode)
|
||||
throw new InternalServiceCallException("InventoryApi", (int)response.StatusCode, responseBody);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(responseBody))
|
||||
return 0;
|
||||
|
||||
using var json = JsonDocument.Parse(responseBody);
|
||||
return json.RootElement.TryGetProperty("reassignedItemCount", out var countElement) && countElement.ValueKind == JsonValueKind.Number
|
||||
? countElement.GetInt32()
|
||||
: 0;
|
||||
}
|
||||
|
||||
private sealed class InternalServiceCallException : Exception
|
||||
{
|
||||
public InternalServiceCallException(string serviceName, int statusCode, string responseBody)
|
||||
: base($"{serviceName} returned {statusCode}")
|
||||
{
|
||||
ServiceName = serviceName;
|
||||
StatusCode = statusCode;
|
||||
ResponseBody = responseBody;
|
||||
}
|
||||
|
||||
public string ServiceName { get; }
|
||||
|
||||
public int StatusCode { get; }
|
||||
|
||||
public string ResponseBody { get; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user