< Summary

Information
Class: Chronicis.Client.Services.PublicApiService
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/Services/PublicApiService.cs
Line coverage
100%
Covered lines: 3
Uncovered lines: 0
Coverable lines: 3
Total lines: 224
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.Client/Services/PublicApiService.cs

#LineLine coverage
 1using System.Net.Http.Json;
 2using Chronicis.Shared.DTOs;
 3using Chronicis.Shared.DTOs.Maps;
 4
 5namespace Chronicis.Client.Services;
 6
 7/// <summary>
 8/// Service for anonymous public API operations.
 9/// Uses a separate HttpClient without authentication headers.
 10/// </summary>
 11public class PublicApiService : IPublicApiService
 12{
 13    private readonly HttpClient _http;
 14    private readonly ILogger<PublicApiService> _logger;
 15
 16    public PublicApiService(HttpClient http, ILogger<PublicApiService> logger)
 17    {
 2218        _http = http;
 2219        _logger = logger;
 2220    }
 21
 22    public async Task<WorldDetailDto?> GetPublicWorldAsync(string publicSlug)
 23    {
 24        try
 25        {
 26            var response = await _http.GetAsync($"public/worlds/{publicSlug}");
 27
 28            if (response.IsSuccessStatusCode)
 29            {
 30                return await response.Content.ReadFromJsonAsync<WorldDetailDto>();
 31            }
 32
 33            if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
 34            {
 35                _logger.LogDebug("Public world not found: {PublicSlug}", publicSlug);
 36                return null;
 37            }
 38
 39            _logger.LogWarning("Failed to get public world {PublicSlug}: {StatusCode}",
 40                publicSlug, response.StatusCode);
 41            return null;
 42        }
 43        catch (Exception ex)
 44        {
 45            _logger.LogError(ex, "Error getting public world {PublicSlug}", publicSlug);
 46            return null;
 47        }
 48    }
 49
 50    public async Task<List<ArticleTreeDto>> GetPublicArticleTreeAsync(string publicSlug)
 51    {
 52        try
 53        {
 54            var response = await _http.GetAsync($"public/worlds/{publicSlug}/articles");
 55
 56            if (response.IsSuccessStatusCode)
 57            {
 58                return await response.Content.ReadFromJsonAsync<List<ArticleTreeDto>>()
 59                    ?? new List<ArticleTreeDto>();
 60            }
 61
 62            _logger.LogWarning("Failed to get public article tree for {PublicSlug}: {StatusCode}",
 63                publicSlug, response.StatusCode);
 64            return new List<ArticleTreeDto>();
 65        }
 66        catch (Exception ex)
 67        {
 68            _logger.LogError(ex, "Error getting public article tree for {PublicSlug}", publicSlug);
 69            return new List<ArticleTreeDto>();
 70        }
 71    }
 72
 73    public async Task<ArticleDto?> GetPublicArticleAsync(string publicSlug, string articlePath)
 74    {
 75        try
 76        {
 77            var response = await _http.GetAsync($"public/worlds/{publicSlug}/articles/{articlePath}");
 78
 79            if (response.IsSuccessStatusCode)
 80            {
 81                return await response.Content.ReadFromJsonAsync<ArticleDto>();
 82            }
 83
 84            if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
 85            {
 86                _logger.LogDebug("Public article not found: {PublicSlug}/{ArticlePath}", publicSlug, articlePath);
 87                return null;
 88            }
 89
 90            _logger.LogWarning("Failed to get public article {PublicSlug}/{ArticlePath}: {StatusCode}",
 91                publicSlug, articlePath, response.StatusCode);
 92            return null;
 93        }
 94        catch (Exception ex)
 95        {
 96            _logger.LogError(ex, "Error getting public article {PublicSlug}/{ArticlePath}", publicSlug, articlePath);
 97            return null;
 98        }
 99    }
 100
 101    public async Task<string?> ResolvePublicArticlePathAsync(string publicSlug, Guid articleId)
 102    {
 103        try
 104        {
 105            var response = await _http.GetAsync($"public/worlds/{publicSlug}/articles/resolve/{articleId}");
 106
 107            if (response.IsSuccessStatusCode)
 108            {
 109                return await response.Content.ReadAsStringAsync();
 110            }
 111
 112            if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
 113            {
 114                _logger.LogDebug("Public article path not found: {PublicSlug}/{ArticleId}", publicSlug, articleId);
 115                return null;
 116            }
 117
 118            _logger.LogWarning("Failed to resolve public article path {PublicSlug}/{ArticleId}: {StatusCode}",
 119                publicSlug, articleId, response.StatusCode);
 120            return null;
 121        }
 122        catch (Exception ex)
 123        {
 124            _logger.LogError(ex, "Error resolving public article path {PublicSlug}/{ArticleId}", publicSlug, articleId);
 125            return null;
 126        }
 127    }
 128
 129    public async Task<(GetBasemapReadUrlResponseDto? Basemap, int? StatusCode, string? Error)> GetPublicMapBasemapReadUr
 130        string publicSlug,
 131        Guid mapId)
 132    {
 133        return await GetEntityWithStatusAsync<GetBasemapReadUrlResponseDto>(
 134            $"public/worlds/{publicSlug}/maps/{mapId}/basemap",
 135            $"public basemap for map {mapId}");
 136    }
 137
 138    public async Task<List<MapLayerDto>> GetPublicMapLayersAsync(string publicSlug, Guid mapId)
 139    {
 140        return await GetListAsync<MapLayerDto>(
 141            $"public/worlds/{publicSlug}/maps/{mapId}/layers",
 142            $"public layers for map {mapId}");
 143    }
 144
 145    public async Task<List<MapPinResponseDto>> GetPublicMapPinsAsync(string publicSlug, Guid mapId)
 146    {
 147        return await GetListAsync<MapPinResponseDto>(
 148            $"public/worlds/{publicSlug}/maps/{mapId}/pins",
 149            $"public pins for map {mapId}");
 150    }
 151
 152    public async Task<List<MapFeatureDto>> GetPublicMapFeaturesAsync(string publicSlug, Guid mapId)
 153    {
 154        return await GetListAsync<MapFeatureDto>(
 155            $"public/worlds/{publicSlug}/maps/{mapId}/features",
 156            $"public features for map {mapId}");
 157    }
 158
 159    private async Task<List<T>> GetListAsync<T>(string url, string description)
 160    {
 161        try
 162        {
 163            var response = await _http.GetAsync(url);
 164
 165            if (response.IsSuccessStatusCode)
 166            {
 167                return await response.Content.ReadFromJsonAsync<List<T>>()
 168                    ?? new List<T>();
 169            }
 170
 171            _logger.LogWarning("Failed to get {Description}: {StatusCode}", description, response.StatusCode);
 172            return new List<T>();
 173        }
 174        catch (Exception ex)
 175        {
 176            _logger.LogError(ex, "Error getting {Description}", description);
 177            return new List<T>();
 178        }
 179    }
 180
 181    private async Task<(T? Entity, int? StatusCode, string? Error)> GetEntityWithStatusAsync<T>(
 182        string url,
 183        string description) where T : class
 184    {
 185        try
 186        {
 187            var response = await _http.GetAsync(url);
 188            var statusCode = (int)response.StatusCode;
 189
 190            if (!response.IsSuccessStatusCode)
 191            {
 192                var error = await TryReadErrorMessageAsync(response);
 193                _logger.LogWarning("Failed to fetch {Description}: {StatusCode}", description, response.StatusCode);
 194                return (null, statusCode, error);
 195            }
 196
 197            var entity = await response.Content.ReadFromJsonAsync<T>();
 198            return (entity, statusCode, null);
 199        }
 200        catch (Exception ex)
 201        {
 202            _logger.LogError(ex, "Error fetching {Description} from {Url}", description, url);
 203            return (null, null, ex.Message);
 204        }
 205    }
 206
 207    private static async Task<string?> TryReadErrorMessageAsync(HttpResponseMessage response)
 208    {
 209        try
 210        {
 211            var payload = await response.Content.ReadFromJsonAsync<Dictionary<string, string>>();
 212            if (payload != null && payload.TryGetValue("error", out var error))
 213            {
 214                return error;
 215            }
 216
 217            return null;
 218        }
 219        catch
 220        {
 221            return null;
 222        }
 223    }
 224}