📄 src/Api/ApiEndpoints.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Slopper.Domain;

namespace Slopper.Api;

public static class ApiEndpoints
{
    extension(IEndpointRouteBuilder endpoints)
    {
        public IEndpointConventionBuilder MapApi()
        {
            var api = endpoints.MapGroup("/api");

            var clips = api.MapGroup("/clips");
            clips.MapGet("/", GetClips).WithDisplayName("Get Latest Clips");
            clips
                .MapGet("/{id}/stream", GetClipStream)
                .WithDisplayName("Get Video Stream for Clip")
                .Produces(StatusCodes.Status200OK, contentType: "video/mp4")
                .Produces(StatusCodes.Status404NotFound);

            return api;
        }
    }

    private static IAsyncEnumerable<Clip> GetClips(
        [FromServices] IClipRepository clipRepository,
        CancellationToken cancellationToken,
        [FromQuery] Guid? after = null,
        [FromQuery, Range(0, 64)] int limit = 10
    ) => clipRepository.GetLatest(after, limit, cancellationToken).Select(Clip.FromDomain);

    private static async Task<IResult> GetClipStream(
        [FromServices] IClipRepository clipRepository,
        [FromRoute] Guid id,
        CancellationToken cancellationToken
    )
    {
        var clip = await clipRepository.Get(id, cancellationToken);
        if (clip is null)
        {
            return Results.NotFound();
        }
        return Results.File(
            clip.Path,
            contentType: "video/mp4",
            fileDownloadName: $"{clip.Id}.mp4",
            lastModified: clip.CreatedAt,
            enableRangeProcessing: true
        );
    }
}