📄 src/Api/YouTubeApiEndpoints.cs
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Slopper.Api.YouTubeAuth;
using Slopper.Domain;

namespace Slopper.Api;

public static class YouTubeApiEndpoints
{
    extension(IEndpointRouteBuilder endpoints)
    {
        public IEndpointConventionBuilder MapYouTubeApi()
        {
            var youtube = endpoints.MapGroup("/youtube").RequireYouTubeAuthorization();

            youtube.MapGet("/login", Login).WithDisplayName("YouTube Login");

            youtube.MapPost("/upload", Upload).WithDisplayName("Upload Latest Clip to YouTube");

            return youtube;
        }
    }

    private static RedirectHttpResult Login() => TypedResults.Redirect("/admin/youtube");

    private static async Task<Results<Ok<UploadResult>, NotFound>> Upload(
        [FromKeyedServices("YouTube")] IUploader uploader,
        [FromServices] IClipRepository clipRepository,
        CancellationToken cancellationToken
    )
    {
        var clip = await clipRepository
            .GetLatest(limit: 1, cancellationToken: cancellationToken)
            .FirstOrDefaultAsync(cancellationToken);

        if (clip is null)
        {
            return TypedResults.NotFound();
        }

        var result = await uploader.Upload(clip, cancellationToken);
        return TypedResults.Ok(UploadResult.FromDomain(result));
    }
}

public sealed record UploadResult(Uri CanonicalUrl)
{
    public static UploadResult FromDomain(Domain.UploadResult uploadResult) => new(uploadResult.CanonicalUrl);
}