using LocationsApi.Models; using LocationsApi.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MongoDB.Driver; using System.Net.Http.Headers; using System.Security.Claims; using System.Text; using System.Text.Json; namespace LocationsApi.Controllers; [ApiController] [Route("api/[controller]")] public class LocationsController : ControllerBase { private readonly LocationStore _locations; private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _configuration; public LocationsController(LocationStore locations, IHttpClientFactory httpClientFactory, IConfiguration configuration) { _locations = locations; _httpClientFactory = httpClientFactory; _configuration = configuration; } [HttpPost] [Authorize(Roles = "SUPER")] public async Task Create([FromBody] CreateLocationRequest req) { if (string.IsNullOrWhiteSpace(req.Name)) return BadRequest("Name required"); if (req.Coord is null) return BadRequest("Coord required"); var location = new Location { Name = req.Name.Trim(), Coord = req.Coord, CreatedUtc = DateTime.UtcNow }; try { await _locations.CreateAsync(location); } catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) { return Conflict("Coord must be unique"); } catch (MongoWriteException ex) when (ex.WriteError.Code == 121) { return BadRequest("Location document failed validation"); } return Ok(location); } [HttpGet] [Authorize(Roles = "SUPER")] public async Task ListMine() { var locations = await _locations.GetAllAsync(); return Ok(locations); } [HttpDelete("{id}")] [Authorize(Roles = "SUPER")] public async Task Delete(string id) { var deleted = await _locations.DeleteAsync(id); if (!deleted) return NotFound(); return Ok("Deleted"); } [HttpPut("{id}")] [Authorize(Roles = "SUPER")] public async Task Update(string id, [FromBody] UpdateLocationRequest req) { if (string.IsNullOrWhiteSpace(req.Name)) return BadRequest("Name required"); if (req.Coord is not null) return BadRequest("Coord cannot be updated"); var updated = await _locations.UpdateNameAsync(id, req.Name.Trim()); if (!updated) return NotFound(); return Ok("Updated"); } [HttpPost("{id}/gather")] [Authorize(Roles = "USER,SUPER")] public async Task Gather(string id, [FromBody] GatherResourceRequest req) { if (string.IsNullOrWhiteSpace(req.CharacterId)) return BadRequest("characterId required"); if (string.IsNullOrWhiteSpace(req.ResourceKey)) return BadRequest("resourceKey required"); var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrWhiteSpace(userId)) return Unauthorized(); var allowAnyOwner = User.IsInRole("SUPER"); var gather = await _locations.GatherResourceAsync(id, req.CharacterId, req.ResourceKey, userId, allowAnyOwner); if (gather.Status == GatherStatus.LocationNotFound) return NotFound("Location not found"); if (gather.Status == GatherStatus.CharacterNotFound) return NotFound("Character not found"); if (gather.Status == GatherStatus.Forbidden) return Forbid(); if (gather.Status == GatherStatus.Invalid) return BadRequest("Character is not at the target location"); if (gather.Status == GatherStatus.ResourceNotFound) return NotFound("Resource not found at location"); if (gather.Status == GatherStatus.ResourceDepleted) return Conflict("Resource is depleted"); var inventoryBaseUrl = (_configuration["Services:InventoryApiBaseUrl"] ?? "http://localhost:5003").TrimEnd('/'); var token = Request.Headers.Authorization.ToString(); var grantBody = JsonSerializer.Serialize(new { itemKey = gather.ResourceKey, quantity = gather.QuantityGranted }); var client = _httpClientFactory.CreateClient(); using var request = new HttpRequestMessage( HttpMethod.Post, $"{inventoryBaseUrl}/api/inventory/by-owner/character/{req.CharacterId}/grant"); request.Content = new StringContent(grantBody, Encoding.UTF8, "application/json"); if (!string.IsNullOrWhiteSpace(token)) request.Headers.Authorization = AuthenticationHeaderValue.Parse(token); using var response = await client.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { await _locations.RestoreGatheredResourceAsync(id, gather.ResourceKey, gather.QuantityGranted); return StatusCode((int)response.StatusCode, responseBody); } return Ok(new GatherResourceResponse { LocationId = id, CharacterId = req.CharacterId, ResourceKey = gather.ResourceKey, QuantityGranted = gather.QuantityGranted, RemainingQuantity = gather.RemainingQuantity, InventoryResponseJson = responseBody }); } }