Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
299 changes: 299 additions & 0 deletions game-sessions/Features/Sessions/Controllers/CommandersController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
namespace game_sessions.Features.Sessions.Controllers;

using Microsoft.AspNetCore.Mvc;
using game_sessions.Features.Sessions.Models;
using game_sessions.Features.Sessions.Services;

/// <summary>
/// Controller for managing commanders
/// SOLID Principle: Single Responsibility Principle (SRP)
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class TotoCommandersController : ControllerBase
{
private readonly ITotoCommanderService _commanderService;

public TotoCommandersController(ITotoCommanderService commanderService)
{
_commanderService = commanderService;
}

/// <summary>
/// Retrieves all commanders
/// </summary>
/// <returns>List of all commanders</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TotoCommander>>> GetAllCommanders()
{
var commanders = await _commanderService.GetAllCommandersAsync();
return Ok(commanders);
}

/// <summary>
/// Retrieves a commander by its ID
/// </summary>
/// <param name="id">The commander identifier</param>
/// <returns>The corresponding commander</returns>
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<TotoCommander>> GetCommanderById(int id)
{
var commander = await _commanderService.GetCommanderByIdAsync(id);
if (commander == null)
{
return NotFound($"Commander with ID {id} not found.");
}
return Ok(commander);
}

/// <summary>
/// Searches for commanders by name
/// </summary>
/// <param name="name">The name or partial name of the commander</param>
/// <returns>List of matching commanders</returns>
[HttpGet("search/{name}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TotoCommander>>> SearchCommandersByName(string name)
{
var commanders = await _commanderService.SearchCommandersByNameAsync(name);
return Ok(commanders);
}

/// <summary>
/// Filters commanders by color identity (exact match)
/// </summary>
/// <param name="colors">The color identity (comma-separated, e.g., "Red,Blue")</param>
/// <returns>List of commanders with matching color identity</returns>
[HttpGet("color-identity")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TotoCommander>>> GetCommandersByColorIdentity([FromQuery] string colors)
{
var colorList = colors.Split(',')
.Select(c => Enum.Parse<ManaColor>(c.Trim(), true))
.ToList();
Comment on lines +74 to +76
Copy link

Copilot AI Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum.Parse will throw an exception for invalid color values. Wrap this in a try-catch block and return a BadRequest with a descriptive error message listing valid ManaColor values when parsing fails.

Suggested change
var colorList = colors.Split(',')
.Select(c => Enum.Parse<ManaColor>(c.Trim(), true))
.ToList();
var colorStrings = colors.Split(',').Select(c => c.Trim()).ToList();
var colorList = new List<ManaColor>();
var invalidColors = new List<string>();
foreach (var colorStr in colorStrings)
{
if (Enum.TryParse<ManaColor>(colorStr, true, out var colorValue))
{
colorList.Add(colorValue);
}
else
{
invalidColors.Add(colorStr);
}
}
if (invalidColors.Any())
{
var validValues = string.Join(", ", Enum.GetNames(typeof(ManaColor)));
return BadRequest($"Invalid color value(s): {string.Join(", ", invalidColors)}. Valid ManaColor values are: {validValues}.");
}

Copilot uses AI. Check for mistakes.

var commanders = await _commanderService.GetCommandersByColorIdentityAsync(colorList);
return Ok(commanders);
}

/// <summary>
/// Filters commanders by single color (contains)
/// </summary>
/// <param name="color">The mana color</param>
/// <returns>List of commanders containing this color in their identity</returns>
[HttpGet("color/{color}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TotoCommander>>> GetCommandersByColor(ManaColor color)
{
var commanders = await _commanderService.GetCommandersByColorAsync(color);
return Ok(commanders);
}

/// <summary>
/// Filters commanders by type
/// </summary>
/// <param name="type">The creature type (e.g., "Dragon", "Elf")</param>
/// <returns>List of commanders of the specified type</returns>
[HttpGet("type/{type}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TotoCommander>>> GetCommandersByType(string type)
{
var commanders = await _commanderService.GetCommandersByTypeAsync(type);
return Ok(commanders);
}

/// <summary>
/// Filters commanders by minimum power
/// </summary>
/// <param name="minPower">The minimum power</param>
/// <returns>List of commanders with at least this power</returns>
[HttpGet("power/{minPower}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TotoCommander>>> GetCommandersByMinPower(int minPower)
{
var commanders = await _commanderService.GetCommandersByMinPowerAsync(minPower);
return Ok(commanders);
}

/// <summary>
/// Filters commanders by minimum toughness
/// </summary>
/// <param name="minToughness">The minimum toughness</param>
/// <returns>List of commanders with at least this toughness</returns>
[HttpGet("toughness/{minToughness}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TotoCommander>>> GetCommandersByMinToughness(int minToughness)
{
var commanders = await _commanderService.GetCommandersByMinToughnessAsync(minToughness);
return Ok(commanders);
}

/// <summary>
/// Retrieves all commanders with partner ability
/// </summary>
/// <returns>List of partner commanders</returns>
[HttpGet("partner")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TotoCommander>>> GetPartnerCommanders()
{
var commanders = await _commanderService.GetPartnerCommandersAsync();
return Ok(commanders);
}

/// <summary>
/// Filters commanders by ability
/// </summary>
/// <param name="ability">The ability to search for</param>
/// <returns>List of commanders with this ability</returns>
[HttpGet("ability/{ability}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TotoCommander>>> GetCommandersByAbility(CreatureAbility ability)
{
var commanders = await _commanderService.GetCommandersByAbilityAsync(ability);
return Ok(commanders);
}

/// <summary>
/// Adds a new commander
/// </summary>
/// <param name="commander">The commander to add</param>
/// <returns>The added commander with its ID</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<TotoCommander>> AddCommander([FromBody] TotoCommander commander)
{
if (string.IsNullOrWhiteSpace(commander.Name))
{
return BadRequest("The commander name is required.");
}

var addedCommander = await _commanderService.AddCommanderAsync(commander);
return CreatedAtAction(nameof(GetCommanderById), new { id = addedCommander.Id }, addedCommander);
}

/// <summary>
/// Updates an existing commander
/// </summary>
/// <param name="id">The commander identifier</param>
/// <param name="commander">The updated data</param>
/// <returns>The result of the operation</returns>
[HttpPut("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> UpdateCommander(int id, [FromBody] TotoCommander commander)
{
var success = await _commanderService.UpdateCommanderAsync(id, commander);
if (!success)
{
return NotFound($"Commander with ID {id} not found.");
}
return NoContent();
}

/// <summary>
/// Deletes a commander
/// </summary>
/// <param name="id">The commander identifier</param>
/// <returns>The result of the operation</returns>
[HttpDelete("{id}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> DeleteCommander(int id)
{
var success = await _commanderService.DeleteCommanderAsync(id);
if (!success)
{
return NotFound($"Commander with ID {id} not found.");
}
return NoContent();
}

/// <summary>
/// Calculates the commander tax for a commander
/// </summary>
/// <param name="id">The commander identifier</param>
/// <returns>The additional mana cost due to commander tax</returns>
[HttpGet("{id}/tax")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<int>> CalculateCommanderTax(int id)
{
var commander = await _commanderService.GetCommanderByIdAsync(id);
if (commander == null)
{
return NotFound($"Commander with ID {id} not found.");
}

var tax = await _commanderService.CalculateCommanderTaxAsync(id);
return Ok(new { CommanderId = id, CommanderTax = tax });
}

/// <summary>
/// Records commander damage dealt to a player
/// </summary>
/// <param name="id">The commander identifier</param>
/// <param name="playerId">The target player identifier</param>
/// <param name="damage">The amount of damage dealt</param>
/// <returns>The result of the operation</returns>
[HttpPost("{id}/damage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> RecordCommanderDamage(int id, [FromQuery] int playerId, [FromQuery] int damage)
{
var success = await _commanderService.RecordCommanderDamageAsync(id, playerId, damage);
if (!success)
{
return NotFound($"Commander with ID {id} not found.");
}

var totalDamage = await _commanderService.GetCommanderDamageToPlayerAsync(id, playerId);
return Ok(new { CommanderId = id, PlayerId = playerId, DamageDealt = damage, TotalDamage = totalDamage });
}

/// <summary>
/// Gets total commander damage dealt to a player
/// </summary>
/// <param name="id">The commander identifier</param>
/// <param name="playerId">The target player identifier</param>
/// <returns>Total damage dealt</returns>
[HttpGet("{id}/damage/{playerId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<int>> GetCommanderDamageToPlayer(int id, int playerId)
{
var commander = await _commanderService.GetCommanderByIdAsync(id);
if (commander == null)
{
return NotFound($"Commander with ID {id} not found.");
}

var totalDamage = await _commanderService.GetCommanderDamageToPlayerAsync(id, playerId);
return Ok(new { CommanderId = id, PlayerId = playerId, TotalDamage = totalDamage });
}

/// <summary>
/// Moves a commander to a different zone
/// </summary>
/// <param name="id">The commander identifier</param>
/// <param name="zone">The destination zone</param>
/// <returns>The result of the operation</returns>
[HttpPost("{id}/move")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> MoveCommanderToZone(int id, [FromQuery] CommanderZone zone)
{
var success = await _commanderService.MoveCommanderToZoneAsync(id, zone);
if (!success)
{
return NotFound($"Commander with ID {id} not found.");
}

var commander = await _commanderService.GetCommanderByIdAsync(id);
return Ok(new { CommanderId = id, CurrentZone = commander!.CurrentZone, CommanderTaxCount = commander.CommanderTaxCount });
Copy link

Copilot AI Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The null-forgiving operator (!) is used after checking success, but there's no guarantee the commander still exists between the MoveCommanderToZoneAsync call and this GetCommanderByIdAsync call in a concurrent environment. The service uses in-memory storage without thread safety, creating a potential race condition.

Suggested change
return Ok(new { CommanderId = id, CurrentZone = commander!.CurrentZone, CommanderTaxCount = commander.CommanderTaxCount });
if (commander == null)
{
return NotFound($"Commander with ID {id} not found after move.");
}
return Ok(new { CommanderId = id, CurrentZone = commander.CurrentZone, CommanderTaxCount = commander.CommanderTaxCount });

Copilot uses AI. Check for mistakes.
}
}

Loading