Keeping logic within microservice boundaries
Deploy Promiscuity Auth API / deploy (push) Successful in 45s
Deploy Promiscuity Character API / deploy (push) Successful in 57s
Deploy Promiscuity Inventory API / deploy (push) Successful in 45s
Deploy Promiscuity Locations API / deploy (push) Successful in 58s
k8s smoke test / test (push) Successful in 8s

This commit is contained in:
2026-03-20 09:11:44 -05:00
parent 1320e0a0ac
commit 8ce6a05710
19 changed files with 720 additions and 531 deletions
@@ -60,6 +60,68 @@ public class LocationsController : ControllerBase
return Ok(location);
}
[HttpGet("biome-definitions")]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> ListBiomeDefinitions()
{
var definitions = await _locations.GetBiomeDefinitionsAsync();
return Ok(definitions);
}
[HttpGet("biome-definitions/{biomeKey}")]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> GetBiomeDefinition(string biomeKey)
{
var normalizedBiomeKey = NormalizeBiomeKey(biomeKey);
if (string.IsNullOrWhiteSpace(normalizedBiomeKey))
return BadRequest("biomeKey required");
var definition = await _locations.GetBiomeDefinitionAsync(normalizedBiomeKey);
return definition is null ? NotFound() : Ok(definition);
}
[HttpPost("internal/visible-window")]
public async Task<IActionResult> GetVisibleWindow([FromBody] InternalVisibleLocationsRequest req)
{
var configuredKey = (_configuration["InternalApi:Key"] ?? _configuration["Jwt:Key"] ?? string.Empty).Trim();
var requestKey = (Request.Headers["X-Internal-Api-Key"].FirstOrDefault() ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(configuredKey) || !string.Equals(configuredKey, requestKey, StringComparison.Ordinal))
return Unauthorized();
if (req.Radius < 0)
return BadRequest("radius must be non-negative");
var result = await _locations.GetOrCreateVisibleLocationsAsync(req.X, req.Y, req.Radius);
return Ok(result);
}
[HttpPost("biome-definitions/{biomeKey}")]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> CreateBiomeDefinition(string biomeKey, [FromBody] UpsertBiomeDefinitionRequest req)
{
var validationError = ValidateBiomeDefinitionRequest(biomeKey, req);
if (validationError is not null)
return validationError;
var definition = BuildBiomeDefinition(biomeKey, req);
var created = await _locations.CreateBiomeDefinitionAsync(definition);
if (!created)
return Conflict("Biome definition already exists");
return Ok(definition);
}
[HttpPut("biome-definitions/{biomeKey}")]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> UpsertBiomeDefinition(string biomeKey, [FromBody] UpsertBiomeDefinitionRequest req)
{
var validationError = ValidateBiomeDefinitionRequest(biomeKey, req);
if (validationError is not null)
return validationError;
var definition = await _locations.UpsertBiomeDefinitionAsync(biomeKey, req);
return Ok(definition);
}
[HttpGet]
[Authorize(Roles = "SUPER")]
public async Task<IActionResult> ListMine()
@@ -254,4 +316,55 @@ public class LocationsController : ControllerBase
});
}
}
private IActionResult? ValidateBiomeDefinitionRequest(string biomeKey, UpsertBiomeDefinitionRequest req)
{
if (string.IsNullOrWhiteSpace(NormalizeBiomeKey(biomeKey)))
return BadRequest("biomeKey required");
if (req.ContinuationWeight < 0)
return BadRequest("continuationWeight must be non-negative");
if (req.TransitionWeights.Any(weight => string.IsNullOrWhiteSpace(weight.TargetBiomeKey)))
return BadRequest("transitionWeights require targetBiomeKey");
if (req.TransitionWeights.Any(weight => weight.Weight < 0))
return BadRequest("transitionWeights weight must be non-negative");
if (req.ObjectSpawnRules.Count == 0)
return BadRequest("objectSpawnRules required");
if (req.ObjectSpawnRules.Any(rule => rule.Weight < 0))
return BadRequest("objectSpawnRules weight must be non-negative");
if (req.ObjectSpawnRules.Any(rule => string.IsNullOrWhiteSpace(rule.ResultType)))
return BadRequest("objectSpawnRules resultType required");
if (req.ObjectSpawnRules.Any(rule =>
string.Equals(rule.ResultType, "gatherable", StringComparison.OrdinalIgnoreCase) &&
string.IsNullOrWhiteSpace(rule.ItemKey)))
return BadRequest("gatherable objectSpawnRules require itemKey");
return null;
}
private static BiomeDefinition BuildBiomeDefinition(string biomeKey, UpsertBiomeDefinitionRequest req)
{
var normalizedBiomeKey = NormalizeBiomeKey(biomeKey);
return new BiomeDefinition
{
BiomeKey = normalizedBiomeKey,
ContinuationWeight = req.ContinuationWeight,
TransitionWeights = req.TransitionWeights.Select(weight => new BiomeTransitionWeight
{
TargetBiomeKey = NormalizeBiomeKey(weight.TargetBiomeKey),
Weight = weight.Weight
}).ToList(),
ObjectSpawnRules = req.ObjectSpawnRules.Select(rule => new BiomeObjectSpawnRule
{
ResultType = rule.ResultType.Trim().ToLowerInvariant(),
ItemKey = string.IsNullOrWhiteSpace(rule.ItemKey) ? null : rule.ItemKey.Trim().ToLowerInvariant(),
ObjectKey = string.IsNullOrWhiteSpace(rule.ObjectKey) ? null : rule.ObjectKey.Trim(),
DisplayName = string.IsNullOrWhiteSpace(rule.DisplayName) ? null : rule.DisplayName.Trim(),
RemainingQuantity = rule.RemainingQuantity,
GatherQuantity = rule.GatherQuantity,
Weight = rule.Weight
}).ToList(),
UpdatedUtc = DateTime.UtcNow
};
}
private static string NormalizeBiomeKey(string biomeKey) => biomeKey.Trim().ToLowerInvariant();
}