< 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
45%
Covered lines: 40
Uncovered lines: 48
Coverable lines: 88
Total lines: 199
Line coverage: 45.4%
Branch coverage
23%
Covered branches: 9
Total branches: 38
Branch coverage: 23.6%
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%
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("health")]
 51    public Task<IActionResult> GetHealthAsync(CancellationToken ct = default) =>
 252        GetGistsQueryAsync(1, null, null, null, null, null, null, ct);
 53
 54    [HttpGet("{id:int}")]
 55    public async Task<IActionResult> GetGistWithFeedByIdAsync(
 56        int id,
 57        [FromQuery] LanguageMode? languageMode = null,
 58        CancellationToken ct = default)
 59    {
 60        try
 61        {
 062            var gist = await mariaDbHandler.GetConstructedGistByIdAsync(id, languageMode, ct);
 063            if (gist is null) return NotFound();
 064            return Ok(gist);
 65        }
 066        catch (Exception e)
 67        {
 68            const string message = "Could not get gist with feed by ID from the database";
 069            logger?.LogError(ErrorInHttpRequest, e, message);
 070            return Problem(message);
 71        }
 072    }
 73
 74    [HttpGet("{id:int}/similar")]
 75    public async Task<IActionResult> GetSimilarGistsWithFeedAsync(
 76        int id,
 77        [FromQuery] string? disabledFeeds = null,
 78        [FromQuery] LanguageMode? languageMode = null,
 79        CancellationToken ct = default)
 80    {
 81        try
 82        {
 383            var gistWithFeed = await mariaDbHandler.GetConstructedGistByIdAsync(id, languageMode, ct);
 484            if (gistWithFeed is null) return NotFound();
 285            var similarityResults =
 286                await chromaDbHandler.GetReferenceAndScoreOfSimilarEntriesAsync(gistWithFeed.Reference, 5,
 287                    ParseDisabledFeeds(disabledFeeds), ct);
 288            var gistsWithFeed = new List<SimilarGistWithFeed>();
 1089            foreach (var similarityResult in similarityResults)
 90            {
 391                gistsWithFeed.Add(await GetSimilarGistWithFeedFromDatabaseAsync(similarityResult, languageMode, ct));
 92            }
 293            return Ok(gistsWithFeed);
 94        }
 095        catch (Exception e)
 96        {
 97            const string message = "Could not get similar gists from the database";
 098            logger?.LogError(ErrorInHttpRequest, e, message);
 099            return Problem(message);
 100        }
 3101    }
 102
 103    private async Task<SimilarGistWithFeed> GetSimilarGistWithFeedFromDatabaseAsync(SimilarDocument similarDocument,
 104        LanguageMode? languageMode, CancellationToken ct)
 105    {
 3106        var gistWithFeed = await mariaDbHandler.GetConstructedGistByReference(similarDocument.Reference, languageMode, c
 3107        if (gistWithFeed is null)
 108        {
 0109            throw new KeyNotFoundException(
 0110                $"Similar gist with reference {similarDocument.Reference} not found in database");
 111        }
 3112        return new SimilarGistWithFeed(gistWithFeed, similarDocument.Similarity);
 3113    }
 114
 115    [HttpGet("feeds")]
 116    public async Task<IActionResult> GetAllFeedsAsync(CancellationToken ct)
 117    {
 118        try
 119        {
 2120            var feeds = await mariaDbHandler.GetAllFeedInfosAsync(ct);
 2121            return Ok(feeds);
 122        }
 0123        catch (Exception e)
 124        {
 125            const string message = "Could not get all feeds from the database";
 0126            logger?.LogError(ErrorInHttpRequest, e, message);
 0127            return Problem(message);
 128        }
 2129    }
 130
 131    [HttpGet("recap/daily")]
 132    public Task<IActionResult> GetDailyRecapAsync([FromQuery] LanguageMode languageMode, CancellationToken ct) =>
 0133        GetRecapAsync(RecapType.Daily, languageMode, ct);
 134
 135    [HttpGet("recap/weekly")]
 136    public Task<IActionResult> GetWeeklyRecapAsync([FromQuery] LanguageMode languageMode, CancellationToken ct) =>
 0137        GetRecapAsync(RecapType.Weekly, languageMode, ct);
 138
 139    private async Task<IActionResult> GetRecapAsync(RecapType type, LanguageMode languageMode, CancellationToken ct)
 140    {
 141        try
 142        {
 0143            if (languageMode == LanguageMode.Original)
 144            {
 0145                throw new ArgumentException("Language mode 'Original' is not supported for recaps");
 146            }
 0147            var serializedRecap = await mariaDbHandler.GetLatestRecapAsync(type, ct);
 0148            if (serializedRecap is null) return NotFound("No recap found in the database");
 0149            var serializedRecapSections =
 0150                languageMode == LanguageMode.En ? serializedRecap.RecapEn : serializedRecap.RecapDe;
 0151            var recapSections =
 0152                JsonSerializer.Deserialize<IEnumerable<RecapSection>>(serializedRecapSections,
 0153                    SerializerDefaults.JsonOptions);
 0154            if (recapSections is null)
 155            {
 0156                throw new JsonException($"Could not deserialize recap from this JSON: {serializedRecapSections}");
 157            }
 158
 0159            recapSections = recapSections.ToList();
 0160            var gistTitlesById = new Dictionary<int, string>();
 0161            var gistIds = recapSections.SelectMany(s => s.Related).Distinct().ToList();
 0162            foreach (var gistId in gistIds)
 163            {
 0164                var gistWithFeed = await mariaDbHandler.GetConstructedGistByIdAsync(gistId, LanguageMode.Original, ct);
 0165                if (gistWithFeed is not null) gistTitlesById[gistId] = gistWithFeed.Title;
 166            }
 167
 0168            var deserializedRecapSections = recapSections.Select(s => new DeserializedRecapSection(s.Heading,
 0169                s.Recap,
 0170                s.Related.Where(r => gistTitlesById.ContainsKey(r))
 0171                    .Select(r => new RelatedGistInfo(r, gistTitlesById[r]))));
 0172            var deserializedRecap = new DeserializedRecap(serializedRecap.Created, deserializedRecapSections,
 0173                serializedRecap.Id ?? -1);
 0174            return Ok(deserializedRecap);
 175        }
 0176        catch (Exception e)
 177        {
 178            const string message = "Could not get recap from the database";
 0179            logger?.LogError(CouldNotGetRecap, e, message);
 0180            return Problem(message);
 181        }
 0182    }
 183
 5184    private static List<string> ParseTags(string? tags) => string.IsNullOrWhiteSpace(tags)
 5185        ? []
 5186        : tags
 5187            .Split(";;", StringSplitOptions.RemoveEmptyEntries)
 2188            .Where(id => !string.IsNullOrWhiteSpace(id))
 7189            .Select(tag => tag.Trim()).ToList();
 190
 7191    private static List<int> ParseDisabledFeeds(string? disabledFeeds) => string.IsNullOrWhiteSpace(disabledFeeds)
 7192        ? []
 7193        : disabledFeeds
 7194            .Split(',', StringSplitOptions.RemoveEmptyEntries)
 8195            .Where(id => !string.IsNullOrWhiteSpace(id))
 8196            .Select(id => id.Trim())
 7197            .Select(int.Parse)
 7198            .ToList();
 199}