All checks were successful
Deploy Promiscuity Auth API / deploy (push) Successful in 47s
Deploy Promiscuity Character API / deploy (push) Successful in 44s
Deploy Promiscuity Inventory API / deploy (push) Successful in 58s
Deploy Promiscuity Locations API / deploy (push) Successful in 58s
k8s smoke test / test (push) Successful in 9s
313 lines
13 KiB
C#
313 lines
13 KiB
C#
using InventoryApi.Models;
|
|
using InventoryApi.Services;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using System.Security.Claims;
|
|
|
|
namespace InventoryApi.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
public class InventoryController : ControllerBase
|
|
{
|
|
private readonly InventoryStore _inventory;
|
|
|
|
public InventoryController(InventoryStore inventory)
|
|
{
|
|
_inventory = inventory;
|
|
}
|
|
|
|
[HttpGet("item-definitions")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> ListItemDefinitions()
|
|
{
|
|
var definitions = await _inventory.ListItemDefinitionsAsync();
|
|
return Ok(definitions.Select(ItemDefinitionResponse.FromModel).ToList());
|
|
}
|
|
|
|
[HttpGet("item-definitions/{itemKey}")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> GetItemDefinition(string itemKey)
|
|
{
|
|
var definition = await _inventory.GetItemDefinitionAsync(itemKey);
|
|
if (definition is null)
|
|
return NotFound();
|
|
|
|
return Ok(ItemDefinitionResponse.FromModel(definition));
|
|
}
|
|
|
|
[HttpPost("item-definitions")]
|
|
[Authorize(Roles = "SUPER")]
|
|
public async Task<IActionResult> CreateItemDefinition([FromBody] CreateItemDefinitionRequest req)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(req.ItemKey))
|
|
return BadRequest("itemKey required");
|
|
if (string.IsNullOrWhiteSpace(req.DisplayName))
|
|
return BadRequest("displayName required");
|
|
if (req.MaxStackSize <= 0)
|
|
return BadRequest("maxStackSize must be greater than 0");
|
|
if (!req.Stackable && req.MaxStackSize != 1)
|
|
return BadRequest("Non-stackable items must have maxStackSize of 1");
|
|
|
|
var created = await _inventory.CreateItemDefinitionAsync(req);
|
|
if (created is null)
|
|
return Conflict("Item definition already exists");
|
|
|
|
return Ok(ItemDefinitionResponse.FromModel(created));
|
|
}
|
|
|
|
[HttpPut("item-definitions/{itemKey}")]
|
|
[Authorize(Roles = "SUPER")]
|
|
public async Task<IActionResult> UpdateItemDefinition(string itemKey, [FromBody] UpdateItemDefinitionRequest req)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(req.DisplayName))
|
|
return BadRequest("displayName required");
|
|
if (req.MaxStackSize <= 0)
|
|
return BadRequest("maxStackSize must be greater than 0");
|
|
if (!req.Stackable && req.MaxStackSize != 1)
|
|
return BadRequest("Non-stackable items must have maxStackSize of 1");
|
|
|
|
var updated = await _inventory.UpdateItemDefinitionAsync(itemKey, req);
|
|
if (updated is null)
|
|
return NotFound();
|
|
|
|
return Ok(ItemDefinitionResponse.FromModel(updated));
|
|
}
|
|
|
|
[HttpGet("by-owner/{ownerType}/{ownerId}")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> GetByOwner(string ownerType, string ownerId)
|
|
{
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
return Unauthorized();
|
|
|
|
var access = await _inventory.ResolveOwnerAsync(ownerType, ownerId, userId, User.IsInRole("SUPER"));
|
|
if (!access.IsSupported)
|
|
return BadRequest("Unsupported ownerType");
|
|
if (!access.Exists)
|
|
return NotFound();
|
|
if (!access.IsAuthorized)
|
|
return Forbid();
|
|
|
|
var items = await _inventory.GetByOwnerAsync(access.OwnerType, access.OwnerId);
|
|
return Ok(new InventoryOwnerResponse
|
|
{
|
|
OwnerType = access.OwnerType,
|
|
OwnerId = access.OwnerId,
|
|
Items = items.Select(InventoryItemResponse.FromModel).ToList()
|
|
});
|
|
}
|
|
|
|
[HttpPost("by-owner/{ownerType}/{ownerId}/grant")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> Grant(string ownerType, string ownerId, [FromBody] GrantInventoryItemRequest req)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(req.ItemKey))
|
|
return BadRequest("itemKey required");
|
|
if (req.Quantity <= 0)
|
|
return BadRequest("quantity must be greater than 0");
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
return Unauthorized();
|
|
|
|
var access = await _inventory.ResolveOwnerAsync(ownerType, ownerId, userId, User.IsInRole("SUPER"));
|
|
if (!access.IsSupported)
|
|
return BadRequest("Unsupported ownerType");
|
|
if (!access.Exists)
|
|
return NotFound();
|
|
if (!access.IsAuthorized)
|
|
return Forbid();
|
|
|
|
var definition = await _inventory.GetItemDefinitionAsync(req.ItemKey);
|
|
if (definition is null)
|
|
return BadRequest("Unknown itemKey");
|
|
|
|
var grant = await _inventory.GrantAsync(access, req, definition);
|
|
return Ok(new InventoryOwnerResponse
|
|
{
|
|
OwnerType = access.OwnerType,
|
|
OwnerId = access.OwnerId,
|
|
RequestedQuantity = grant.RequestedQuantity,
|
|
GrantedQuantity = grant.GrantedQuantity,
|
|
OverflowQuantity = grant.OverflowQuantity,
|
|
Items = grant.Items.Select(InventoryItemResponse.FromModel).ToList()
|
|
});
|
|
}
|
|
|
|
[HttpPost("by-owner/{ownerType}/{ownerId}/move")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> Move(string ownerType, string ownerId, [FromBody] MoveInventoryItemRequest req)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(req.ItemId))
|
|
return BadRequest("itemId required");
|
|
if (req.ToSlot < 0)
|
|
return BadRequest("toSlot must be >= 0");
|
|
if (req.Quantity is <= 0)
|
|
return BadRequest("quantity must be greater than 0");
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
return Unauthorized();
|
|
|
|
var access = await _inventory.ResolveOwnerAsync(ownerType, ownerId, userId, User.IsInRole("SUPER"));
|
|
if (!access.IsSupported)
|
|
return BadRequest("Unsupported ownerType");
|
|
if (!access.Exists)
|
|
return NotFound();
|
|
if (!access.IsAuthorized)
|
|
return Forbid();
|
|
|
|
var result = await _inventory.MoveAsync(access, req);
|
|
return result.Status switch
|
|
{
|
|
InventoryMutationStatus.ItemNotFound => NotFound(),
|
|
InventoryMutationStatus.Invalid => BadRequest("Invalid move"),
|
|
InventoryMutationStatus.Conflict => Conflict("Target slot is not available"),
|
|
_ => Ok(new InventoryOwnerResponse
|
|
{
|
|
OwnerType = access.OwnerType,
|
|
OwnerId = access.OwnerId,
|
|
Items = result.Items.Select(InventoryItemResponse.FromModel).ToList()
|
|
})
|
|
};
|
|
}
|
|
|
|
[HttpPost("transfer")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> Transfer([FromBody] TransferInventoryItemRequest req)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(req.ItemId))
|
|
return BadRequest("itemId required");
|
|
if (string.IsNullOrWhiteSpace(req.FromOwnerType) || string.IsNullOrWhiteSpace(req.FromOwnerId))
|
|
return BadRequest("from owner required");
|
|
if (string.IsNullOrWhiteSpace(req.ToOwnerType) || string.IsNullOrWhiteSpace(req.ToOwnerId))
|
|
return BadRequest("to owner required");
|
|
if (req.ToSlot is < 0)
|
|
return BadRequest("toSlot must be >= 0");
|
|
if (req.Quantity is <= 0)
|
|
return BadRequest("quantity must be greater than 0");
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
return Unauthorized();
|
|
|
|
var fromAccess = await _inventory.ResolveOwnerAsync(req.FromOwnerType, req.FromOwnerId, userId, User.IsInRole("SUPER"));
|
|
if (!fromAccess.IsSupported)
|
|
return BadRequest("Unsupported fromOwnerType");
|
|
if (!fromAccess.Exists)
|
|
return NotFound("Source owner not found");
|
|
if (!fromAccess.IsAuthorized)
|
|
return Forbid();
|
|
|
|
var toAccess = await _inventory.ResolveOwnerAsync(req.ToOwnerType, req.ToOwnerId, userId, User.IsInRole("SUPER"));
|
|
if (!toAccess.IsSupported)
|
|
return BadRequest("Unsupported toOwnerType");
|
|
if (!toAccess.Exists)
|
|
return NotFound("Target owner not found");
|
|
if (!toAccess.IsAuthorized)
|
|
return Forbid();
|
|
|
|
var result = await _inventory.TransferAsync(fromAccess, toAccess, req);
|
|
return result.Status switch
|
|
{
|
|
InventoryMutationStatus.ItemNotFound => NotFound(),
|
|
InventoryMutationStatus.Invalid => BadRequest("Invalid transfer"),
|
|
InventoryMutationStatus.Conflict => Conflict("Target slot is not available"),
|
|
_ => Ok(new TransferInventoryResponse
|
|
{
|
|
MovedItemId = req.ItemId,
|
|
FromOwnerType = fromAccess.OwnerType,
|
|
FromOwnerId = fromAccess.OwnerId,
|
|
ToOwnerType = toAccess.OwnerType,
|
|
ToOwnerId = toAccess.OwnerId,
|
|
FromItems = result.Items.Select(InventoryItemResponse.FromModel).Where(x => x.OwnerType == fromAccess.OwnerType && x.OwnerId == fromAccess.OwnerId).ToList(),
|
|
ToItems = result.Items.Select(InventoryItemResponse.FromModel).Where(x => x.OwnerType == toAccess.OwnerType && x.OwnerId == toAccess.OwnerId).ToList()
|
|
})
|
|
};
|
|
}
|
|
|
|
[HttpPost("items/{itemId}/consume")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> Consume(string itemId, [FromBody] ConsumeInventoryItemRequest req)
|
|
{
|
|
if (req.Quantity <= 0)
|
|
return BadRequest("quantity must be greater than 0");
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
return Unauthorized();
|
|
|
|
var result = await _inventory.ConsumeAsync(itemId, req.Quantity, userId, User.IsInRole("SUPER"));
|
|
return result.Status switch
|
|
{
|
|
InventoryMutationStatus.ItemNotFound => NotFound(),
|
|
InventoryMutationStatus.Invalid => BadRequest("Invalid consume request"),
|
|
InventoryMutationStatus.Conflict => Conflict(),
|
|
_ => Ok(new InventoryOwnerResponse
|
|
{
|
|
OwnerType = result.OwnerType,
|
|
OwnerId = result.OwnerId,
|
|
Items = result.Items.Select(InventoryItemResponse.FromModel).ToList()
|
|
})
|
|
};
|
|
}
|
|
|
|
[HttpPost("items/{itemId}/equip")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> Equip(string itemId, [FromBody] EquipInventoryItemRequest req)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(req.OwnerId))
|
|
return BadRequest("ownerId required");
|
|
if (string.IsNullOrWhiteSpace(req.EquipmentSlot))
|
|
return BadRequest("equipmentSlot required");
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
return Unauthorized();
|
|
|
|
var result = await _inventory.EquipAsync(itemId, req.OwnerId, req.EquipmentSlot, userId, User.IsInRole("SUPER"));
|
|
return result.Status switch
|
|
{
|
|
InventoryMutationStatus.ItemNotFound => NotFound(),
|
|
InventoryMutationStatus.Invalid => BadRequest("Invalid equip request"),
|
|
InventoryMutationStatus.Conflict => Conflict("Equipment slot is not available"),
|
|
_ => Ok(new InventoryOwnerResponse
|
|
{
|
|
OwnerType = result.OwnerType,
|
|
OwnerId = result.OwnerId,
|
|
Items = result.Items.Select(InventoryItemResponse.FromModel).ToList()
|
|
})
|
|
};
|
|
}
|
|
|
|
[HttpPost("items/{itemId}/unequip")]
|
|
[Authorize(Roles = "USER,SUPER")]
|
|
public async Task<IActionResult> Unequip(string itemId, [FromBody] UnequipInventoryItemRequest req)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(req.OwnerId))
|
|
return BadRequest("ownerId required");
|
|
if (req.PreferredSlot is < 0)
|
|
return BadRequest("preferredSlot must be >= 0");
|
|
|
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
|
if (string.IsNullOrWhiteSpace(userId))
|
|
return Unauthorized();
|
|
|
|
var result = await _inventory.UnequipAsync(itemId, req.OwnerId, req.PreferredSlot, userId, User.IsInRole("SUPER"));
|
|
return result.Status switch
|
|
{
|
|
InventoryMutationStatus.ItemNotFound => NotFound(),
|
|
InventoryMutationStatus.Invalid => BadRequest("Invalid unequip request"),
|
|
InventoryMutationStatus.Conflict => Conflict("Inventory slot is not available"),
|
|
_ => Ok(new InventoryOwnerResponse
|
|
{
|
|
OwnerType = result.OwnerType,
|
|
OwnerId = result.OwnerId,
|
|
Items = result.Items.Select(InventoryItemResponse.FromModel).ToList()
|
|
})
|
|
};
|
|
}
|
|
}
|