< Summary

Information
Class: Chronicis.Api.Controllers.PublicController
Assembly: Chronicis.Api
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Api/Controllers/PublicController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 42
Coverable lines: 42
Total lines: 130
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 20
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%
GetPublicWorld()0%2040%
GetPublicArticleTree()0%4260%
GetPublicArticle()0%4260%
ResolveArticlePath()0%2040%

File(s)

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

#LineLine coverage
 1using Chronicis.Api.Services;
 2using Chronicis.Shared.DTOs;
 3using Chronicis.Shared.Extensions;
 4using Microsoft.AspNetCore.Mvc;
 5
 6namespace Chronicis.Api.Controllers;
 7
 8/// <summary>
 9/// API endpoints for anonymous public access to worlds and articles.
 10/// These endpoints do NOT require authentication.
 11/// </summary>
 12[ApiController]
 13[Route("public")]
 14public class PublicController : ControllerBase
 15{
 16    private readonly IPublicWorldService _publicWorldService;
 17    private readonly ILogger<PublicController> _logger;
 18
 019    public PublicController(
 020        IPublicWorldService publicWorldService,
 021        ILogger<PublicController> logger)
 22    {
 023        _publicWorldService = publicWorldService;
 024        _logger = logger;
 025    }
 26
 27    /// <summary>
 28    /// GET /api/public/worlds/{publicSlug} - Get a public world by its public slug.
 29    /// </summary>
 30    [HttpGet("worlds/{publicSlug}")]
 31    public async Task<ActionResult<WorldDetailDto>> GetPublicWorld(string publicSlug)
 32    {
 033        if (string.IsNullOrWhiteSpace(publicSlug))
 34        {
 035            return BadRequest(new { error = "Public slug is required" });
 36        }
 37
 038        _logger.LogDebugSanitized("Getting public world with slug '{PublicSlug}'", publicSlug);
 39
 040        var world = await _publicWorldService.GetPublicWorldAsync(publicSlug);
 41
 042        if (world == null)
 43        {
 044            return NotFound(new { error = "World not found or not public" });
 45        }
 46
 047        return Ok(world);
 048    }
 49
 50    /// <summary>
 51    /// GET /api/public/worlds/{publicSlug}/articles - Get the article tree for a public world.
 52    /// Returns a hierarchical tree structure with virtual groups (Campaigns, Player Characters, Wiki).
 53    /// Only returns articles with Public visibility.
 54    /// </summary>
 55    [HttpGet("worlds/{publicSlug}/articles")]
 56    public async Task<ActionResult<List<ArticleTreeDto>>> GetPublicArticleTree(string publicSlug)
 57    {
 058        if (string.IsNullOrWhiteSpace(publicSlug))
 59        {
 060            return BadRequest(new { error = "Public slug is required" });
 61        }
 62
 063        _logger.LogDebugSanitized("Getting public article tree for world '{PublicSlug}'", publicSlug);
 64
 065        var tree = await _publicWorldService.GetPublicArticleTreeAsync(publicSlug);
 66
 67        // If tree is empty, check if world exists
 068        if (!tree.Any())
 69        {
 070            var world = await _publicWorldService.GetPublicWorldAsync(publicSlug);
 071            if (world == null)
 72            {
 073                return NotFound(new { error = "World not found or not public" });
 74            }
 75        }
 76
 077        return Ok(tree);
 078    }
 79
 80    /// <summary>
 81    /// GET /api/public/worlds/{publicSlug}/articles/{*articlePath} - Get a specific public article by path.
 82    /// </summary>
 83    [HttpGet("worlds/{publicSlug}/articles/{*articlePath}")]
 84    public async Task<ActionResult<ArticleDto>> GetPublicArticle(string publicSlug, string articlePath)
 85    {
 086        if (string.IsNullOrWhiteSpace(publicSlug))
 87        {
 088            return BadRequest(new { error = "Public slug is required" });
 89        }
 90
 091        if (string.IsNullOrWhiteSpace(articlePath))
 92        {
 093            return BadRequest(new { error = "Article path is required" });
 94        }
 95
 096        _logger.LogDebugSanitized("Getting public article '{ArticlePath}' in world '{PublicSlug}'", articlePath, publicS
 97
 098        var article = await _publicWorldService.GetPublicArticleAsync(publicSlug, articlePath);
 99
 0100        if (article == null)
 101        {
 0102            return NotFound(new { error = "Article not found or not public" });
 103        }
 104
 0105        return Ok(article);
 0106    }
 107
 108    /// <summary>
 109    /// GET /api/public/worlds/{publicSlug}/articles/resolve/{articleId} - Resolve an article ID to its public URL path.
 110    /// </summary>
 111    [HttpGet("worlds/{publicSlug}/articles/resolve/{articleId:guid}")]
 112    public async Task<ActionResult<string>> ResolveArticlePath(string publicSlug, Guid articleId)
 113    {
 0114        if (string.IsNullOrWhiteSpace(publicSlug))
 115        {
 0116            return BadRequest(new { error = "Public slug is required" });
 117        }
 118
 0119        _logger.LogDebugSanitized("Resolving article path for {ArticleId} in world '{PublicSlug}'", articleId, publicSlu
 120
 0121        var path = await _publicWorldService.GetPublicArticlePathAsync(publicSlug, articleId);
 122
 0123        if (path == null)
 124        {
 0125            return NotFound(new { error = "Article not found or not public" });
 126        }
 127
 0128        return Ok(path);
 0129    }
 130}