📄 src/Domain/ClipDetailsService.cs
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Slopper.Domain;

public sealed class ClipDetailsService(
    ILogger<ClipDetailsService> logger,
    IClipRepository clipRepository,
    IMediaRepository mediaRepository
)
{
    public async IAsyncEnumerable<ClipDetails> GetLatest(
        Guid? after = null,
        int limit = 10,
        [EnumeratorCancellation] CancellationToken cancellationToken = default
    )
    {
        await foreach (var clip in clipRepository.GetLatest(after, limit, cancellationToken))
        {
            MediaInfo? mediaInfo = null;
            try
            {
                mediaInfo = await mediaRepository.GetMediaInfo(clip.MediaItemId, cancellationToken);
            }
            catch (Exception ex) when (ex is not TaskCanceledException)
            {
                logger.LogWarning(
                    ex,
                    "Could not fetch media info for {ClipId} ({MediaItemId})",
                    clip.Id,
                    clip.MediaItemId
                );
            }
            yield return new ClipDetails(clip, mediaInfo);
        }
    }
}

public static class ClipDetailsServiceCollectionExtensions
{
    extension(IServiceCollection services)
    {
        public IServiceCollection AddClipDetailsService()
        {
            services.AddTransient<ClipDetailsService>();
            return services;
        }
    }
}