< Summary

Information
Class: Chronicis.Api.Controllers.CharactersController
Assembly: Chronicis.Api
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Api/Controllers/CharactersController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 78
Coverable lines: 78
Total lines: 169
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 14
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetClaimedCharacters()100%210%
GetClaimStatus()0%2040%
ClaimCharacter()0%7280%
UnclaimCharacter()0%620%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Api/Controllers/CharactersController.cs

#LineLine coverage
 1using Chronicis.Api.Data;
 2using Chronicis.Api.Infrastructure;
 3using Chronicis.Shared.DTOs;
 4using Chronicis.Shared.Enums;
 5using Microsoft.AspNetCore.Authorization;
 6using Microsoft.AspNetCore.Mvc;
 7using Microsoft.EntityFrameworkCore;
 8
 9namespace Chronicis.Api.Controllers;
 10
 11/// <summary>
 12/// API endpoints for Character claiming operations.
 13/// </summary>
 14[ApiController]
 15[Route("characters")]
 16[Authorize]
 17public class CharactersController : ControllerBase
 18{
 19    private readonly ChronicisDbContext _context;
 20    private readonly ICurrentUserService _currentUserService;
 21    private readonly ILogger<CharactersController> _logger;
 22
 023    public CharactersController(
 024        ChronicisDbContext context,
 025        ICurrentUserService currentUserService,
 026        ILogger<CharactersController> logger)
 27    {
 028        _context = context;
 029        _currentUserService = currentUserService;
 030        _logger = logger;
 031    }
 32
 33    /// <summary>
 34    /// GET /api/characters/claimed - Get all characters claimed by the current user.
 35    /// </summary>
 36    [HttpGet("claimed")]
 37    public async Task<ActionResult<List<ClaimedCharacterDto>>> GetClaimedCharacters()
 38    {
 039        var user = await _currentUserService.GetRequiredUserAsync();
 040        _logger.LogDebug("Getting claimed characters for user {UserId}", user.Id);
 41
 042        var claimedCharacters = await _context.Articles
 043            .Where(a => a.PlayerId == user.Id && a.Type == ArticleType.Character)
 044            .Where(a => a.WorldId.HasValue)
 045            .Select(a => new ClaimedCharacterDto
 046            {
 047                Id = a.Id,
 048                Title = a.Title ?? "Unnamed Character",
 049                IconEmoji = a.IconEmoji,
 050                WorldId = a.WorldId!.Value,
 051                WorldName = a.World != null ? a.World.Name : "Unknown World",
 052                ModifiedAt = a.ModifiedAt,
 053                CreatedAt = a.CreatedAt
 054            })
 055            .ToListAsync();
 56
 057        return Ok(claimedCharacters);
 058    }
 59
 60    /// <summary>
 61    /// GET /api/characters/{id}/claim - Get the claim status of a character.
 62    /// </summary>
 63    [HttpGet("{id:guid}/claim")]
 64    public async Task<ActionResult<CharacterClaimStatusDto>> GetClaimStatus(Guid id)
 65    {
 066        var user = await _currentUserService.GetRequiredUserAsync();
 067        _logger.LogDebug("Getting claim status for character {CharacterId}", id);
 68
 069        var article = await _context.Articles
 070            .Where(a => a.Id == id && a.Type == ArticleType.Character)
 071            .Select(a => new
 072            {
 073                a.Id,
 074                a.PlayerId,
 075                PlayerName = a.Player != null ? a.Player.DisplayName : null
 076            })
 077            .FirstOrDefaultAsync();
 78
 079        if (article == null)
 80        {
 081            return NotFound(new { error = "Character not found" });
 82        }
 83
 084        var status = new CharacterClaimStatusDto
 085        {
 086            CharacterId = article.Id,
 087            IsClaimed = article.PlayerId.HasValue,
 088            IsClaimedByMe = article.PlayerId == user.Id,
 089            ClaimedByName = article.PlayerName
 090        };
 91
 092        return Ok(status);
 093    }
 94
 95    /// <summary>
 96    /// POST /api/characters/{id}/claim - Claim a character for the current user.
 97    /// </summary>
 98    [HttpPost("{id:guid}/claim")]
 99    public async Task<IActionResult> ClaimCharacter(Guid id)
 100    {
 0101        var user = await _currentUserService.GetRequiredUserAsync();
 0102        _logger.LogDebug("User {UserId} claiming character {CharacterId}", user.Id, id);
 103
 104        // Get the character article and verify user has access to its world
 0105        var article = await _context.Articles
 0106            .Where(a => a.Id == id && a.Type == ArticleType.Character)
 0107            .Where(a => a.WorldId.HasValue && a.World!.Members.Any(m => m.UserId == user.Id))
 0108            .FirstOrDefaultAsync();
 109
 0110        if (article == null)
 111        {
 0112            return NotFound(new { error = "Character not found or access denied" });
 113        }
 114
 115        // Check if already claimed by someone else
 0116        if (article.PlayerId.HasValue && article.PlayerId != user.Id)
 117        {
 0118            return Conflict(new { error = "Character is already claimed by another player" });
 119        }
 120
 121        // Claim the character
 0122        article.PlayerId = user.Id;
 0123        article.ModifiedAt = DateTime.UtcNow;
 0124        await _context.SaveChangesAsync();
 125
 0126        _logger.LogDebug("Character {CharacterId} claimed by user {UserId}", id, user.Id);
 0127        return NoContent();
 0128    }
 129
 130    /// <summary>
 131    /// DELETE /api/characters/{id}/claim - Unclaim a character (remove current user's claim).
 132    /// </summary>
 133    [HttpDelete("{id:guid}/claim")]
 134    public async Task<IActionResult> UnclaimCharacter(Guid id)
 135    {
 0136        var user = await _currentUserService.GetRequiredUserAsync();
 0137        _logger.LogDebug("User {UserId} unclaiming character {CharacterId}", user.Id, id);
 138
 139        // Get the character article
 0140        var article = await _context.Articles
 0141            .Where(a => a.Id == id && a.Type == ArticleType.Character)
 0142            .Where(a => a.PlayerId == user.Id) // Only allow unclaiming own characters
 0143            .FirstOrDefaultAsync();
 144
 0145        if (article == null)
 146        {
 0147            return NotFound(new { error = "Character not found or not claimed by you" });
 148        }
 149
 150        // Unclaim the character
 0151        article.PlayerId = null;
 0152        article.ModifiedAt = DateTime.UtcNow;
 0153        await _context.SaveChangesAsync();
 154
 0155        _logger.LogDebug("Character {CharacterId} unclaimed by user {UserId}", id, user.Id);
 0156        return NoContent();
 0157    }
 158}
 159
 160/// <summary>
 161/// Status of a character's claim.
 162/// </summary>
 163public class CharacterClaimStatusDto
 164{
 165    public Guid CharacterId { get; set; }
 166    public bool IsClaimed { get; set; }
 167    public bool IsClaimedByMe { get; set; }
 168    public string? ClaimedByName { get; set; }
 169}