< Summary

Information
Class: Chronicis.Api.Controllers.WorldLinksController
Assembly: Chronicis.Api
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Api/Controllers/WorldLinksController.cs
Line coverage
100%
Covered lines: 8
Uncovered lines: 0
Coverable lines: 8
Total lines: 121
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%

File(s)

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

#LineLine coverage
 1using Chronicis.Api.Infrastructure;
 2using Chronicis.Api.Models;
 3using Chronicis.Api.Services;
 4using Chronicis.Shared.DTOs;
 5using Microsoft.AspNetCore.Authorization;
 6using Microsoft.AspNetCore.Mvc;
 7
 8namespace Chronicis.Api.Controllers;
 9
 10/// <summary>
 11/// API endpoints for World Link management (external resource links).
 12/// </summary>
 13[ApiController]
 14[Route("worlds/{worldId:guid}/links")]
 15[Authorize]
 16public class WorldLinksController : ControllerBase
 17{
 18    private readonly IWorldLinkService _worldLinkService;
 19    private readonly ICurrentUserService _currentUserService;
 20    private readonly ILogger<WorldLinksController> _logger;
 21
 122    public WorldLinksController(
 123        IWorldLinkService worldLinkService,
 124        ICurrentUserService currentUserService,
 125        ILogger<WorldLinksController> logger)
 26    {
 127        _worldLinkService = worldLinkService;
 128        _currentUserService = currentUserService;
 129        _logger = logger;
 130    }
 31
 32    /// <summary>
 33    /// GET /worlds/{worldId}/links - Get all links for a world.
 34    /// </summary>
 35    [HttpGet]
 36    public async Task<ActionResult<IEnumerable<WorldLinkDto>>> GetWorldLinks(Guid worldId)
 37    {
 38        var user = await _currentUserService.GetRequiredUserAsync();
 39        _logger.LogTraceSanitized("Getting links for world {WorldId} by user {UserId}", worldId, user.Id);
 40        var result = await _worldLinkService.GetWorldLinksAsync(worldId, user.Id);
 41        return result.Status == ServiceStatus.NotFound
 42            ? NotFound(new { error = result.ErrorMessage })
 43            : Ok(result.Value);
 44    }
 45
 46    /// <summary>
 47    /// POST /worlds/{worldId}/links - Create a new link for a world.
 48    /// </summary>
 49    [HttpPost]
 50    public async Task<ActionResult<WorldLinkDto>> CreateWorldLink(Guid worldId, [FromBody] WorldLinkCreateDto dto)
 51    {
 52        var user = await _currentUserService.GetRequiredUserAsync();
 53
 54        if (dto == null || string.IsNullOrWhiteSpace(dto.Url) || string.IsNullOrWhiteSpace(dto.Title))
 55        {
 56            return BadRequest(new { error = "URL and Title are required" });
 57        }
 58
 59        // Validate URL format
 60        if (!Uri.TryCreate(dto.Url, UriKind.Absolute, out var uri) ||
 61            (uri.Scheme != "http" && uri.Scheme != "https"))
 62        {
 63            return BadRequest(new { error = "Invalid URL format. Must be a valid http or https URL." });
 64        }
 65
 66        _logger.LogTraceSanitized("Creating link '{Title}' for world {WorldId} by user {UserId}",
 67            dto.Title, worldId, user.Id);
 68
 69        var result = await _worldLinkService.CreateWorldLinkAsync(worldId, dto, user.Id);
 70        return result.Status == ServiceStatus.NotFound
 71            ? NotFound(new { error = result.ErrorMessage })
 72            : CreatedAtAction(nameof(GetWorldLinks), new { worldId }, result.Value);
 73    }
 74
 75    /// <summary>
 76    /// PUT /worlds/{worldId}/links/{linkId} - Update an existing world link.
 77    /// </summary>
 78    [HttpPut("{linkId:guid}")]
 79    public async Task<ActionResult<WorldLinkDto>> UpdateWorldLink(
 80        Guid worldId,
 81        Guid linkId,
 82        [FromBody] WorldLinkUpdateDto dto)
 83    {
 84        var user = await _currentUserService.GetRequiredUserAsync();
 85
 86        if (dto == null || string.IsNullOrWhiteSpace(dto.Url) || string.IsNullOrWhiteSpace(dto.Title))
 87        {
 88            return BadRequest(new { error = "URL and Title are required" });
 89        }
 90
 91        // Validate URL format
 92        if (!Uri.TryCreate(dto.Url, UriKind.Absolute, out var uri) ||
 93            (uri.Scheme != "http" && uri.Scheme != "https"))
 94        {
 95            return BadRequest(new { error = "Invalid URL format. Must be a valid http or https URL." });
 96        }
 97
 98        _logger.LogTraceSanitized("Updating link {LinkId} for world {WorldId} by user {UserId}", linkId, worldId, user.I
 99
 100        var result = await _worldLinkService.UpdateWorldLinkAsync(worldId, linkId, dto, user.Id);
 101        return result.Status == ServiceStatus.NotFound
 102            ? NotFound(new { error = result.ErrorMessage })
 103            : Ok(result.Value);
 104    }
 105
 106    /// <summary>
 107    /// DELETE /worlds/{worldId}/links/{linkId} - Delete a world link.
 108    /// </summary>
 109    [HttpDelete("{linkId:guid}")]
 110    public async Task<IActionResult> DeleteWorldLink(Guid worldId, Guid linkId)
 111    {
 112        var user = await _currentUserService.GetRequiredUserAsync();
 113
 114        _logger.LogTraceSanitized("Deleting link {LinkId} for world {WorldId} by user {UserId}",
 115            linkId, worldId, user.Id);
 116        var result = await _worldLinkService.DeleteWorldLinkAsync(worldId, linkId, user.Id);
 117        return result.Status == ServiceStatus.NotFound
 118            ? NotFound(new { error = result.ErrorMessage })
 119            : NoContent();
 120    }
 121}