All checks were successful
Deploy Promiscuity Auth API / deploy (push) Successful in 48s
Deploy Promiscuity Character API / deploy (push) Successful in 46s
Deploy Promiscuity Crafting API / deploy (push) Successful in 1m9s
Deploy Promiscuity Inventory API / deploy (push) Successful in 46s
Deploy Promiscuity Locations API / deploy (push) Successful in 48s
Deploy Promiscuity Mail API / deploy (push) Successful in 46s
k8s smoke test / test (push) Successful in 8s
136 lines
5.3 KiB
C#
136 lines
5.3 KiB
C#
using CraftingApi.Models;
|
|
using CraftingApi.Services;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using System.Security.Claims;
|
|
|
|
namespace CraftingApi.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
public class CraftingController : ControllerBase
|
|
{
|
|
private readonly CraftingStore _crafting;
|
|
|
|
public CraftingController(CraftingStore crafting)
|
|
{
|
|
_crafting = crafting;
|
|
}
|
|
|
|
[HttpGet("recipes")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> ListRecipes()
|
|
{
|
|
var recipes = await _crafting.ListRecipesAsync();
|
|
return Ok(recipes.Select(CraftingRecipeResponse.FromModel).ToList());
|
|
}
|
|
|
|
[HttpGet("recipes/{recipeKey}")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> GetRecipe(string recipeKey)
|
|
{
|
|
var recipe = await _crafting.GetRecipeAsync(recipeKey);
|
|
return recipe is null ? NotFound() : Ok(CraftingRecipeResponse.FromModel(recipe));
|
|
}
|
|
|
|
[HttpPost("recipes/{recipeKey}")]
|
|
[Authorize(Roles = "SUPER")]
|
|
public async Task<IActionResult> CreateRecipe(string recipeKey, [FromBody] UpsertCraftingRecipeRequest request)
|
|
{
|
|
var validationError = ValidateRecipeRequest(recipeKey, request);
|
|
if (validationError is not null)
|
|
return validationError;
|
|
|
|
var recipe = _crafting.BuildRecipe(recipeKey, request);
|
|
var created = await _crafting.CreateRecipeAsync(recipe);
|
|
return created ? Ok(CraftingRecipeResponse.FromModel(recipe)) : Conflict("Recipe already exists");
|
|
}
|
|
|
|
[HttpPut("recipes/{recipeKey}")]
|
|
[Authorize(Roles = "SUPER")]
|
|
public async Task<IActionResult> UpsertRecipe(string recipeKey, [FromBody] UpsertCraftingRecipeRequest request)
|
|
{
|
|
var validationError = ValidateRecipeRequest(recipeKey, request);
|
|
if (validationError is not null)
|
|
return validationError;
|
|
|
|
var recipe = await _crafting.UpsertRecipeAsync(recipeKey, request);
|
|
return Ok(CraftingRecipeResponse.FromModel(recipe));
|
|
}
|
|
|
|
[HttpGet("characters/{characterId}/available-recipes")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> AvailableRecipes(string characterId)
|
|
{
|
|
var access = await ResolveCharacterAccessAsync(characterId);
|
|
if (access is IActionResult errorResult)
|
|
return errorResult;
|
|
|
|
var available = await _crafting.GetAvailableRecipesAsync((CraftingStore.CharacterAccessResult)access!);
|
|
return Ok(available);
|
|
}
|
|
|
|
[HttpPost("characters/{characterId}/craft")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> Craft(string characterId, [FromBody] CraftRecipeRequest request)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(request.RecipeKey))
|
|
return BadRequest("recipeKey required");
|
|
if (request.Quantity <= 0)
|
|
return BadRequest("quantity must be greater than 0");
|
|
|
|
var access = await ResolveCharacterAccessAsync(characterId);
|
|
if (access is IActionResult errorResult)
|
|
return errorResult;
|
|
|
|
var result = await _crafting.CraftAsync((CraftingStore.CharacterAccessResult)access!, request);
|
|
return result.Status switch
|
|
{
|
|
CraftingStore.CraftStatus.RecipeNotFound => NotFound("Recipe not found"),
|
|
CraftingStore.CraftStatus.MissingStation => Conflict("Required crafting station is not available"),
|
|
CraftingStore.CraftStatus.MissingInputs => Conflict(new { missingRequirements = result.MissingRequirements }),
|
|
CraftingStore.CraftStatus.InvalidRecipe => BadRequest(new { missingRequirements = result.MissingRequirements }),
|
|
_ => Ok(new CraftRecipeResponse
|
|
{
|
|
CharacterId = characterId,
|
|
RecipeKey = request.RecipeKey.Trim().ToLowerInvariant(),
|
|
CraftedQuantity = result.CraftedQuantity,
|
|
Consumed = result.Consumed,
|
|
Produced = result.Produced
|
|
})
|
|
};
|
|
}
|
|
|
|
private async Task<object> ResolveCharacterAccessAsync(string characterId)
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
return Unauthorized();
|
|
|
|
var access = await _crafting.ResolveCharacterAsync(characterId, userId, User.IsInRole("SUPER"));
|
|
if (!access.Exists)
|
|
return NotFound();
|
|
if (!access.IsAuthorized)
|
|
return Forbid();
|
|
|
|
return access;
|
|
}
|
|
|
|
private IActionResult? ValidateRecipeRequest(string recipeKey, UpsertCraftingRecipeRequest request)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(recipeKey))
|
|
return BadRequest("recipeKey required");
|
|
if (string.IsNullOrWhiteSpace(request.Name))
|
|
return BadRequest("name required");
|
|
if (request.Inputs.Count == 0)
|
|
return BadRequest("At least one input is required");
|
|
if (request.Outputs.Count == 0)
|
|
return BadRequest("At least one output is required");
|
|
if (request.Inputs.Any(i => string.IsNullOrWhiteSpace(i.ItemKey) || i.Quantity <= 0))
|
|
return BadRequest("All inputs must have itemKey and quantity > 0");
|
|
if (request.Outputs.Any(i => string.IsNullOrWhiteSpace(i.ItemKey) || i.Quantity <= 0))
|
|
return BadRequest("All outputs must have itemKey and quantity > 0");
|
|
return null;
|
|
}
|
|
}
|