chore: group .NET into backend/ and drop non-.NET test artifacts

- Move all .NET (6 projects, solution, Directory.*.props, nuget.config)
  into backend/ so the monorepo root can host non-.NET projects later
- Remove throwaway test artifacts: player.html, pl.json
- Trim dead Flutter rules from .gitignore

Build from backend/Musify.slnx: 0 warnings, 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 03:43:52 +02:00
co-authored by Claude Opus 4.8
parent d9c8c17b21
commit 8f34ed4874
247 changed files with 0 additions and 229 deletions
@@ -0,0 +1,70 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Musify.Application.Configuration;
using Musify.Application.Contracts;
namespace Musify.Infrastructure.Jobs
{
public class TemporalUploadsCleanUpJob(
IServiceScopeFactory scopeFactory,
ILogger<TemporalUploadsCleanUpJob> logger,
ApplicationStorageConfiguration storageConfiguration,
UploadIntentConfiguration uploadIntentConfiguration)
: BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("TemporalUploadsCleanUpJob started (interval: {Interval}s, prefix: {Prefix})",
uploadIntentConfiguration.TemporalCleanUpJobIntervalSeconds,
uploadIntentConfiguration.TemporalRootPrefix);
while (!stoppingToken.IsCancellationRequested)
{
try
{
await RunAsync(stoppingToken);
}
catch (Exception exception) when (exception is not OperationCanceledException)
{
logger.LogError(exception, "Error in TemporalUploadsCleanUpJob");
}
await Task.Delay(TimeSpan.FromSeconds(uploadIntentConfiguration.TemporalCleanUpJobIntervalSeconds), stoppingToken);
}
}
async Task RunAsync(CancellationToken cancellationToken)
{
await using var scope = scopeFactory.CreateAsyncScope();
var storageService = scope.ServiceProvider.GetRequiredService<IStorageService>();
var cutOff = DateTime.UtcNow.AddDays(-uploadIntentConfiguration.TemporalUploadsRetentionDays);
var deleted = 0;
var errors = 0;
await foreach (var (key, lastModified) in storageService.ListObjectsAsync(
storageConfiguration.Bucket,
uploadIntentConfiguration.TemporalRootPrefix,
cancellationToken))
{
if (lastModified >= cutOff)
continue;
try
{
await storageService.RemoveFileAsync(storageConfiguration.Bucket, key, cancellationToken);
deleted++;
}
catch (Exception exception)
{
errors++;
logger.LogWarning(exception, "Failed to delete orphaned temp object {Key}", key);
}
}
if (deleted > 0 || errors > 0)
logger.LogInformation("TemporalUploadsCleanUpJob: deleted {Deleted} orphaned objects, {Errors} errors", deleted, errors);
}
}
}
@@ -0,0 +1,73 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Musify.Application.Configuration;
using Musify.Domain.ValueObjects;
using Musify.Application.Contracts;
namespace Musify.Infrastructure.Jobs
{
public class UploadIntentExpirationJob(
IServiceScopeFactory scopeFactory,
ILogger<UploadIntentExpirationJob> logger,
UploadIntentConfiguration uploadIntentConfiguration)
: BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("UploadIntentExpirationJob started (interval: {Interval}s)", uploadIntentConfiguration.ExpirationJobIntervalSeconds);
while (!stoppingToken.IsCancellationRequested)
{
try
{
await RunAsync(stoppingToken);
}
catch (Exception exception) when (exception is not OperationCanceledException)
{
logger.LogError(exception, "Error in UploadIntentExpirationJob");
}
await Task.Delay(TimeSpan.FromSeconds(uploadIntentConfiguration.ExpirationJobIntervalSeconds), stoppingToken);
}
}
async Task RunAsync(CancellationToken cancellationToken)
{
await using var scope = scopeFactory.CreateAsyncScope();
var database = scope.ServiceProvider.GetRequiredService<IDatabase>();
var now = DateTime.UtcNow;
var expiredIntents = await database.UploadIntents
.Where(i => i.Status == UploadIntentStatus.Issued && i.ExpiresAt < now)
.ToListAsync(cancellationToken);
if (expiredIntents.Count > 0)
{
foreach (var intent in expiredIntents)
intent.Status = UploadIntentStatus.Expired;
await database.SaveChangesAsync(cancellationToken);
logger.LogInformation("Marked {Count} upload intents as Expired", expiredIntents.Count);
}
if (uploadIntentConfiguration.ExpiredIntentsRetentionDays >= 0)
{
var retentionCutoff = now.AddDays(-uploadIntentConfiguration.ExpiredIntentsRetentionDays);
var oldExpiredIntents = await database.UploadIntents
.Where(i => i.Status == UploadIntentStatus.Expired && i.ExpiresAt < retentionCutoff)
.ToListAsync(cancellationToken);
if (oldExpiredIntents.Count > 0)
{
database.UploadIntents.RemoveRange(oldExpiredIntents);
await database.SaveChangesAsync(cancellationToken);
logger.LogInformation("Deleted {Count} old Expired upload intents past retention window", oldExpiredIntents.Count);
}
}
}
}
}