< Summary

Information
Class: GistBackend.Controllers.GistsController
Assembly: GistBackend
File(s): /home/runner/work/the-gist-of-it-sec/the-gist-of-it-sec/backend/GistBackend/Controllers/GistsController.cs
Line coverage
40%
Covered lines: 40
Uncovered lines: 59
Coverable lines: 99
Total lines: 224
Line coverage: 40.4%
Branch coverage
21%
Covered branches: 9
Total branches: 42
Branch coverage: 21.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetGistsQueryAsync()0%2257.14%
SearchSimilarGistsAsync()0%2040%
GetHealthAsync(...)100%11100%
GetGistWithFeedByIdAsync()0%2040%
GetSimilarGistsWithFeedAsync()66.66%6676.92%
GetSimilarGistWithFeedFromDatabaseAsync()50%2266.66%
GetAllFeedsAsync()0%3250%
GetDailyRecapAsync(...)100%210%
GetWeeklyRecapAsync(...)100%210%
GetRecapAsync()0%210140%
ParseTags(...)100%22100%
ParseDisabledFeeds(...)100%22100%

File(s)

/home/runner/work/the-gist-of-it-sec/the-gist-of-it-sec/backend/GistBackend/Controllers/GistsController.cs

#LineLine coverage
 1using System.Text.Json;
 2using GistBackend.Handlers.ChromaDbHandler;
 3using GistBackend.Handlers.MariaDbHandler;
 4using GistBackend.Types;
 5using GistBackend.Utils;
 6using Microsoft.AspNetCore.Mvc;
 7using Microsoft.Extensions.DependencyInjection;
 8using Microsoft.Extensions.Logging;
 9using static GistBackend.Utils.LogEvents;
 10
 11namespace GistBackend.Controllers;
 12
 13public static class RoutingConstants
 14{
 15    public const string GistsRoute = "/api/v1/gists";
 16}
 17
 18[ApiController]
 19[Route(RoutingConstants.GistsRoute)]
 1020public class GistsController(
 1021    [FromKeyedServices(StartUp.GistsControllerMariaDbHandlerOptionsName)] IMariaDbHandler mariaDbHandler,
 1022    IChromaDbHandler chromaDbHandler,
 1023    ILogger<GistsController>? logger) : ControllerBase
 24{
 25    [HttpGet]
 26    public async Task<IActionResult> GetGistsQueryAsync(
 27        [FromQuery] int take = 20,
 28        [FromQuery] int? lastGist = null,
 29        [FromQuery] string? tags = null,
 30        [FromQuery] string? q = null,
 31        [FromQuery] string? disabledFeeds = null,
 32        [FromQuery] LanguageMode? languageMode = null,
 33        [FromQuery] bool? includeSponsoredContent = null,
 34        CancellationToken ct = default)
 35    {
 36        try
 37        {
 538            var gists = await mariaDbHandler.GetPreviousConstructedGistsAsync(take, lastGist, ParseTags(tags), q,
 539                ParseDisabledFeeds(disabledFeeds), languageMode, includeSponsoredContent, ct);
 540            return Ok(gists);
 41        }
 042        catch (Exception e)
 43        {
 44            const string message = "Could not get gists from the database";
 045            logger?.LogError(ErrorInHttpRequest, e, message);
 046            return Problem(message);
 47        }
 548    }
 49
 50    [HttpGet("search")]
 51    public async Task<IActionResult> SearchSimilarGistsAsync(
 52        [FromQuery] string? q = null,
 53        [FromQuery] string? disabledFeeds = null,
 54        [FromQuery] LanguageMode? languageMode = null,
 55        CancellationToken ct = default)
 56    {
 57        try
 58        {
 059            if (string.IsNullOrEmpty(q)) return BadRequest("Query parameter 'q' cannot be empty.");
 060            var similarDocuments =
 061                await chromaDbHandler.SearchSimilarEntriesByQueryAsync(q, 20, ParseDisabledFeeds(disabledFeeds), ct);
 062            var gists = await Task.WhenAll(similarDocuments.Select(sd => mariaDbHandler.GetConstructedGistByReference(sd
 063            var searchResults = gists.Zip(similarDocuments)
 064                .Select(zipped => new GistSearchResult(zipped.First!, zipped.Second.Similarity)).ToList();
 065            return Ok(searchResults);
 66        }
 067        catch (Exception e)
 68        {
 69            const string message = "Could not get gists from the database";
 070            logger?.LogError(ErrorInHttpRequest, e, message);
 071            return Problem(message);
 72        }
 073    }
 74
 75    [HttpGet("health")]
 76    public Task<IActionResult> GetHealthAsync(CancellationToken ct = default) =>
 277        GetGistsQueryAsync(1, null, null, null, null, null, null, ct);
 78
 79    [HttpGet("{id:int}")]
 80    public async Task<IActionResult> GetGistWithFeedByIdAsync(
 81        int id,
 82        [FromQuery] LanguageMode? languageMode = null,
 83        CancellationToken ct = default)
 84    {
 85        try
 86        {
 087            var gist = await mariaDbHandler.GetConstructedGistByIdAsync(id, languageMode, ct);
 088            if (gist is null) return NotFound();
 089            return Ok(gist);
 90        }
 091        catch (Exception e)
 92        {
 93            const string message = "Could not get gist with feed by ID from the database";
 094            logger?.LogError(ErrorInHttpRequest, e, message);
 095            return Problem(message);
 96        }
 097    }
 98
 99    [HttpGet("{id:int}/similar")]
 100    public async Task<IActionResult> GetSimilarGistsWithFeedAsync(
 101        int id,
 102        [FromQuery] string? disabledFeeds = null,
 103        [FromQuery] LanguageMode? languageMode = null,
 104        CancellationToken ct = default)
 105    {
 106        try
 107        {
 3108            var gistWithFeed = await mariaDbHandler.GetConstructedGistByIdAsync(id, languageMode, ct);
 4109            if (gistWithFeed is null) return NotFound();
 2110            var similarityResults =
 2111                await chromaDbHandler.GetReferenceAndScoreOfSimilarEntriesAsync(gistWithFeed.Reference, 5,
 2112                    ParseDisabledFeeds(disabledFeeds), ct);
 2113            var gistsWithFeed = new List<SimilarGistWithFeed>();
 10114            foreach (var similarityResult in similarityResults)
 115            {
 3116                gistsWithFeed.Add(await GetSimilarGistWithFeedFromDatabaseAsync(similarityResult, languageMode, ct));
 117            }
 2118            return Ok(gistsWithFeed);
 119        }
 0120        catch (Exception e)
 121        {
 122            const string message = "Could not get similar gists from the database";
 0123            logger?.LogError(ErrorInHttpRequest, e, message);
 0124            return Problem(message);
 125        }
 3126    }
 127
 128    private async Task<SimilarGistWithFeed> GetSimilarGistWithFeedFromDatabaseAsync(SimilarDocument similarDocument,
 129        LanguageMode? languageMode, CancellationToken ct)
 130    {
 3131        var gistWithFeed = await mariaDbHandler.GetConstructedGistByReference(similarDocument.Reference, languageMode, c
 3132        if (gistWithFeed is null)
 133        {
 0134            throw new KeyNotFoundException(
 0135                $"Similar gist with reference {similarDocument.Reference} not found in database");
 136        }
 3137        return new SimilarGistWithFeed(gistWithFeed, similarDocument.Similarity);
 3138    }
 139
 140    [HttpGet("feeds")]
 141    public async Task<IActionResult> GetAllFeedsAsync(CancellationToken ct)
 142    {
 143        try
 144        {
 2145            var feeds = await mariaDbHandler.GetAllFeedInfosAsync(ct);
 2146            return Ok(feeds);
 147        }
 0148        catch (Exception e)
 149        {
 150            const string message = "Could not get all feeds from the database";
 0151            logger?.LogError(ErrorInHttpRequest, e, message);
 0152            return Problem(message);
 153        }
 2154    }
 155
 156    [HttpGet("recap/daily")]
 157    public Task<IActionResult> GetDailyRecapAsync([FromQuery] LanguageMode languageMode, CancellationToken ct) =>
 0158        GetRecapAsync(RecapType.Daily, languageMode, ct);
 159
 160    [HttpGet("recap/weekly")]
 161    public Task<IActionResult> GetWeeklyRecapAsync([FromQuery] LanguageMode languageMode, CancellationToken ct) =>
 0162        GetRecapAsync(RecapType.Weekly, languageMode, ct);
 163
 164    private async Task<IActionResult> GetRecapAsync(RecapType type, LanguageMode languageMode, CancellationToken ct)
 165    {
 166        try
 167        {
 0168            if (languageMode == LanguageMode.Original)
 169            {
 0170                throw new ArgumentException("Language mode 'Original' is not supported for recaps");
 171            }
 0172            var serializedRecap = await mariaDbHandler.GetLatestRecapAsync(type, ct);
 0173            if (serializedRecap is null) return NotFound("No recap found in the database");
 0174            var serializedRecapSections =
 0175                languageMode == LanguageMode.En ? serializedRecap.RecapEn : serializedRecap.RecapDe;
 0176            var recapSections =
 0177                JsonSerializer.Deserialize<IEnumerable<RecapSection>>(serializedRecapSections,
 0178                    SerializerDefaults.JsonOptions);
 0179            if (recapSections is null)
 180            {
 0181                throw new JsonException($"Could not deserialize recap from this JSON: {serializedRecapSections}");
 182            }
 183
 0184            recapSections = recapSections.ToList();
 0185            var gistTitlesById = new Dictionary<int, string>();
 0186            var gistIds = recapSections.SelectMany(s => s.Related).Distinct().ToList();
 0187            foreach (var gistId in gistIds)
 188            {
 0189                var gistWithFeed = await mariaDbHandler.GetConstructedGistByIdAsync(gistId, LanguageMode.Original, ct);
 0190                if (gistWithFeed is not null) gistTitlesById[gistId] = gistWithFeed.Title;
 191            }
 192
 0193            var deserializedRecapSections = recapSections.Select(s => new DeserializedRecapSection(s.Heading,
 0194                s.Recap,
 0195                s.Related.Where(r => gistTitlesById.ContainsKey(r))
 0196                    .Select(r => new RelatedGistInfo(r, gistTitlesById[r]))));
 0197            var deserializedRecap = new DeserializedRecap(serializedRecap.Created, deserializedRecapSections,
 0198                serializedRecap.Id ?? -1);
 0199            return Ok(deserializedRecap);
 200        }
 0201        catch (Exception e)
 202        {
 203            const string message = "Could not get recap from the database";
 0204            logger?.LogError(CouldNotGetRecap, e, message);
 0205            return Problem(message);
 206        }
 0207    }
 208
 5209    private static List<string> ParseTags(string? tags) => string.IsNullOrWhiteSpace(tags)
 5210        ? []
 5211        : tags
 5212            .Split(";;", StringSplitOptions.RemoveEmptyEntries)
 2213            .Where(id => !string.IsNullOrWhiteSpace(id))
 7214            .Select(tag => tag.Trim()).ToList();
 215
 7216    private static List<int> ParseDisabledFeeds(string? disabledFeeds) => string.IsNullOrWhiteSpace(disabledFeeds)
 7217        ? []
 7218        : disabledFeeds
 7219            .Split(',', StringSplitOptions.RemoveEmptyEntries)
 8220            .Where(id => !string.IsNullOrWhiteSpace(id))
 8221            .Select(id => id.Trim())
 7222            .Select(int.Parse)
 7223            .ToList();
 224}