< Summary

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

#LineLine coverage
 1using Chronicis.Api.Data;
 2using Chronicis.Shared.Models;
 3using Microsoft.EntityFrameworkCore;
 4
 5namespace Chronicis.Api.Services;
 6
 7/// <summary>
 8/// Synchronizes wiki links in the database based on article content.
 9/// Uses a delete-then-insert strategy for simplicity.
 10/// </summary>
 11public sealed class LinkSyncService : ILinkSyncService
 12{
 13    private readonly ChronicisDbContext _context;
 14    private readonly ILinkParser _linkParser;
 15    private readonly ILogger<LinkSyncService> _logger;
 16
 17    public LinkSyncService(
 18        ChronicisDbContext context,
 19        ILinkParser linkParser,
 20        ILogger<LinkSyncService> logger)
 21    {
 1222        _context = context;
 1223        _linkParser = linkParser;
 1224        _logger = logger;
 1225    }
 26
 27    /// <summary>
 28    /// Synchronizes the ArticleLink table for the given article.
 29    /// Removes all existing links for this article and creates new ones based on the body content.
 30    /// </summary>
 31    /// <param name="sourceArticleId">The ID of the article whose links should be synced.</param>
 32    /// <param name="body">The article body containing wiki links to parse.</param>
 33    public async Task SyncLinksAsync(Guid sourceArticleId, string? body)
 34    {
 35        // Step 1: Delete all existing links where this article is the source
 36        var existingLinks = await _context.ArticleLinks
 37            .Where(al => al.SourceArticleId == sourceArticleId)
 38            .ToListAsync();
 39
 40        var removedCount = existingLinks.Count;
 41
 42        if (existingLinks.Any())
 43        {
 44            _context.ArticleLinks.RemoveRange(existingLinks);
 45        }
 46
 47        // Step 2: Parse new links from body
 48        var parsedLinks = _linkParser.ParseLinks(body);
 49
 50        // Step 3: Create new ArticleLink entities
 51        var newLinks = parsedLinks.Select(pl => new ArticleLink
 52        {
 53            Id = Guid.NewGuid(),
 54            SourceArticleId = sourceArticleId,
 55            TargetArticleId = pl.TargetArticleId,
 56            DisplayText = pl.DisplayText,
 57            Position = pl.Position,
 58            CreatedAt = DateTime.UtcNow
 59        }).ToList();
 60
 61        var addedCount = newLinks.Count;
 62
 63        if (newLinks.Any())
 64        {
 65            await _context.ArticleLinks.AddRangeAsync(newLinks);
 66        }
 67
 68        // Step 4: Save all changes in a single transaction
 69        await _context.SaveChangesAsync();
 70
 71        // Step 5: Log metrics
 72        _logger.LogTraceSanitized(
 73            "Synced links for article {ArticleId}: {RemovedCount} removed, {AddedCount} added",
 74            sourceArticleId,
 75            removedCount,
 76            addedCount);
 77    }
 78}