Commit: bb30b37
Parent: 3a545d7

Upload all not-yet-uploaded clips

Mårten Åsberg committed on 2026-05-14 at 16:00
src/Api/YouTubeApiEndpoints.cs +5 -8
diff --git a/src/Api/YouTubeApiEndpoints.cs b/src/Api/YouTubeApiEndpoints.cs
index 261f8a0..5fa5059 100644
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
@@ -20,7 +21,7 @@ public static class YouTubeApiEndpoints
youtube.MapGet("/login", Login).WithDisplayName("YouTube Login");
youtube.MapPost("/upload", Upload).WithDisplayName("Upload Latest Clip to YouTube");
youtube.MapPost("/upload", UploadAll).WithDisplayName("Upload Clips to YouTube");
return youtube;
}
@@ -28,12 +29,8 @@ public static class YouTubeApiEndpoints
private static RedirectHttpResult Login() => TypedResults.Redirect("/admin/youtube");
private static async Task<Results<Ok<Upload>, NotFound>> Upload(
private static IAsyncEnumerable<Upload> UploadAll(
[FromServices] Uploader uploader,
CancellationToken cancellationToken
)
{
var result = await uploader.Upload("YouTube", cancellationToken);
return result is null ? TypedResults.NotFound() : TypedResults.Ok(Api.Upload.FromDomain(result));
}
) => uploader.Upload("YouTube", cancellationToken).Select(Upload.FromDomain);
}
src/Domain/IClipRepository.cs +2 -0
diff --git a/src/Domain/IClipRepository.cs b/src/Domain/IClipRepository.cs
index 4d698c2..bca5483 100644
@@ -11,6 +11,8 @@ public interface IClipRepository
IAsyncEnumerable<Clip> GetCreatedBefore(DateTimeOffset before, CancellationToken cancellationToken = default);
IAsyncEnumerable<Clip> GetNotUploadedTo(string platform, CancellationToken cancellationToken = default);
Task<Clip?> Get(Guid id, CancellationToken cancellationToken);
Task Save(Clip clip, CancellationToken cancellationToken);
src/Domain/Uploader.cs +13 -15
diff --git a/src/Domain/Uploader.cs b/src/Domain/Uploader.cs
index 3065f59..aad45b6 100644
@@ -1,30 +1,28 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace Slopper.Domain;
public sealed class Uploader(IServiceProvider serviceProvider, IClipRepository clipRepository)
{
public async Task<Upload?> Upload(string platform, CancellationToken cancellationToken)
public async IAsyncEnumerable<Upload> Upload(
string platform,
[EnumeratorCancellation] CancellationToken cancellationToken
)
{
var uploader = serviceProvider.GetRequiredKeyedService<IUploader>(platform);
var clip = await clipRepository
.GetLatest(limit: 1, cancellationToken: cancellationToken)
.FirstOrDefaultAsync(cancellationToken);
if (clip is null)
return null;
var upload = await uploader.Upload(clip, cancellationToken);
clip.Uploads.Add(upload);
await clipRepository.Save(clip, cancellationToken);
await foreach (var clip in clipRepository.GetNotUploadedTo(platform, cancellationToken))
{
var upload = await uploader.Upload(clip, cancellationToken);
clip.Uploads.Add(upload);
await clipRepository.Save(clip, cancellationToken);
return upload;
yield return upload;
}
}
}
src/Infrastructure/Database/Slopper/ClipRepository.cs +9 -0
diff --git a/src/Infrastructure/Database/Slopper/ClipRepository.cs b/src/Infrastructure/Database/Slopper/ClipRepository.cs
index c212186..7c4d150 100644
@@ -29,6 +29,15 @@ internal sealed class ClipRepository(SlopperDbContext slopperContext) : IClipRep
.Clips.Include(c => c.Tags)
.Include(c => c.Uploads)
.Where(c => c.CreatedAt < before && c.RemovedAt == null)
.OrderByDescending(c => c.Id)
.AsAsyncEnumerable();
public IAsyncEnumerable<Clip> GetNotUploadedTo(string platform, CancellationToken cancellationToken) =>
slopperContext
.Clips.Include(c => c.Tags)
.Include(c => c.Uploads)
.Where(c => c.RemovedAt == null && !c.Uploads.Any(u => u.Platform == platform))
.OrderByDescending(c => c.Id)
.AsAsyncEnumerable();
public async Task<Clip?> Get(Guid id, CancellationToken cancellationToken) =>