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 ListRecipes() { var recipes = await _crafting.ListRecipesAsync(); return Ok(recipes.Select(CraftingRecipeResponse.FromModel).ToList()); } [HttpGet("recipes/{recipeKey}")] [Authorize(Roles = "USER,SUPER")] public async Task 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 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 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 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 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 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; } }