A lot of changes

This commit is contained in:
2026-07-17 18:56:23 +02:00
parent aac9224c2d
commit 4fed9bd059
35 changed files with 2127 additions and 185 deletions
@@ -49,10 +49,13 @@ namespace Musify.Application.PlayLists
return Error.Conflict(description: "Track is already in the playlist.");
}
var nextPosition = await database.PlayListHasTracks
.Where(plt => plt.PlayListId == request.PlayListId)
.Select(plt => (int?)plt.Position)
.MaxAsync(cancellationToken) + 1 ?? 0;
var playListTracksQuery = database.PlayListHasTracks
.Where(plt => plt.PlayListId == request.PlayListId);
var hasTracks = await playListTracksQuery.AnyAsync(cancellationToken);
var nextPosition = hasTracks
? await playListTracksQuery.MaxAsync(plt => plt.Position, cancellationToken) + 1
: 0;
await database.PlayListHasTracks.AddAsync(new PlayListHasTrack
{
@@ -51,7 +51,7 @@ namespace Musify.Application.PlayLists
var track = provisionResult.Value.Track;
var failed = track.Audio.TranscodeStatus == ProcessingStatus.Failed;
var failed = track.Audio.IsFailed;
var neverQueued = !track.Audio.DownloadRequested && !track.Audio.IsProcessed;
if (failed || neverQueued)
{
@@ -82,10 +82,13 @@ namespace Musify.Application.PlayLists
return Error.Conflict(description: "Track is already in the playlist.");
}
var nextPosition = await database.PlayListHasTracks
.Where(plt => plt.PlayListId == request.PlayListId)
.Select(plt => (int?)plt.Position)
.MaxAsync(cancellationToken) + 1 ?? 0;
var playListTracksQuery = database.PlayListHasTracks
.Where(plt => plt.PlayListId == request.PlayListId);
var hasTracks = await playListTracksQuery.AnyAsync(cancellationToken);
var nextPosition = hasTracks
? await playListTracksQuery.MaxAsync(plt => plt.Position, cancellationToken) + 1
: 0;
await database.PlayListHasTracks.AddAsync(new PlayListHasTrack
{
@@ -8,6 +8,7 @@ using Musify.Application.Events;
using Musify.Application.PlayLists.Responses;
using Musify.Application.Services;
using Musify.Domain.Entities;
using Musify.Domain.ValueObjects;
using Musify.Application.Contracts;
namespace Musify.Application.PlayLists
@@ -65,10 +66,13 @@ namespace Musify.Application.PlayLists
Name = request.Name,
NormalizedName = request.Name.Trim().ToUpperInvariant(),
Description = request.Description,
OriginalPictureName = originalPictureName,
SmallPictureName = playListConfiguration.Routes.PresetSmallPicture,
MediumPictureName = playListConfiguration.Routes.PresetMediumPicture,
LargePictureName = playListConfiguration.Routes.PresetLargePicture
Pictures = new PlayListPictures
{
OriginalName = originalPictureName,
SmallName = playListConfiguration.Routes.PresetSmallPicture,
MediumName = playListConfiguration.Routes.PresetMediumPicture,
LargeName = playListConfiguration.Routes.PresetLargePicture
}
};
await database.PlayLists.AddAsync(playList, cancellationToken);
@@ -22,7 +22,7 @@ namespace Musify.Application.PlayLists
{
var playList = await database.PlayLists.AsNoTracking()
.Where(p => p.Id == request.PlayListId)
.Select(p => new { p.SmallPictureName, p.MediumPictureName, p.LargePictureName })
.Select(p => new { p.Pictures.SmallName, p.Pictures.MediumName, p.Pictures.LargeName })
.SingleOrDefaultAsync(cancellationToken);
if (playList == null)
@@ -31,9 +31,9 @@ namespace Musify.Application.PlayLists
var routes = playListConfiguration.Routes;
var (name, key, presetName) = request.Size.ToLowerInvariant() switch
{
"small" => (playList.SmallPictureName, BuildKey(routes.BuildSmallPicturePath, playList.SmallPictureName), routes.PresetSmallPicture),
"large" => (playList.LargePictureName, BuildKey(routes.BuildLargePicturePath, playList.LargePictureName), routes.PresetLargePicture),
_ => (playList.MediumPictureName, BuildKey(routes.BuildMediumPicturePath, playList.MediumPictureName), routes.PresetMediumPicture)
"small" => (playList.SmallName, BuildKey(routes.BuildSmallPicturePath, playList.SmallName), routes.PresetSmallPicture),
"large" => (playList.LargeName, BuildKey(routes.BuildLargePicturePath, playList.LargeName), routes.PresetLargePicture),
_ => (playList.MediumName, BuildKey(routes.BuildMediumPicturePath, playList.MediumName), routes.PresetMediumPicture)
};
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(key) || name == presetName)
@@ -18,9 +18,9 @@ namespace Musify.Application.PlayLists.Responses
playList.Id,
playList.Name,
playList.Description,
playList.SmallPictureName,
playList.MediumPictureName,
playList.LargePictureName,
playList.Pictures.SmallName,
playList.Pictures.MediumName,
playList.Pictures.LargeName,
playList.CreatedAt,
playList.UpdatedAt);
}
@@ -68,7 +68,7 @@ namespace Musify.Application.PlayLists
return validation.Errors;
pictureIntent = validation.Value;
playListEntity.OriginalPictureName = pictureIntent.ObjectName;
playListEntity.Pictures.OriginalName = pictureIntent.ObjectName;
finalPictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(request.UserId, pictureIntent.ObjectName);
}
@@ -45,10 +45,10 @@ public sealed class UploadIntentValidator(
if (intent is null || intent.UserId != userId)
return Error.NotFound(description: "Upload intent not found or not accessible.");
if (intent.Status == UploadIntentStatus.Consumed)
if (intent.IsConsumed)
return Error.Conflict(description: "Upload intent has already been consumed.");
if (intent.Status == UploadIntentStatus.Expired || intent.ExpiresAt < DateTime.UtcNow)
if (intent.IsExpired)
return Error.Validation(description: "Upload intent has expired.");
var metadata = await storageService.HeadObjectAsync(intent.Bucket, intent.Key, cancellationToken);
@@ -50,7 +50,7 @@ public sealed class YouTubeTrackProvisioner(
NormalizedTitle = title.ToUpperInvariant(),
Source = TrackSource.YouTube,
ExternalId = videoId,
Duration = song.DurationSeconds,
DurationSeconds = song.DurationSeconds,
Pictures = new TrackPictures
{
SmallName = trackConfiguration.Routes.PresetSmallPicture,
@@ -34,8 +34,7 @@ namespace Musify.Application.Tracks
return Error.Unauthorized();
}
if (track.Audio.TranscodeStatus == ProcessingStatus.Processing
|| track.Audio.TranscodeStatus == ProcessingStatus.Pending)
if (track.Audio.IsInProgress)
{
logger.LogWarning("Track {TrackId} is currently being processed and cannot be deleted", request.TrackId);
return Error.Conflict(description: "Track is currently being processed and cannot be deleted");
@@ -5,7 +5,6 @@ using Microsoft.Extensions.Logging;
using Musify.Application.Contracts;
using Musify.Application.Services;
using Musify.Application.Tracks.Responses;
using Musify.Domain.ValueObjects;
namespace Musify.Application.Tracks
{
@@ -22,20 +21,19 @@ namespace Musify.Application.Tracks
{
var track = await database.Tracks.AsNoTracking()
.Where(t => t.Id == request.TrackId)
.Select(t => new { t.Id, t.Audio.TranscodeStatus, t.Audio.FolderName } )
.Select(t => new { t.Id, t.Audio })
.SingleOrDefaultAsync(cancellationToken);
if (track == null)
return Error.NotFound();
if (string.IsNullOrWhiteSpace(track.FolderName)
|| track.TranscodeStatus != ProcessingStatus.Completed)
if (!track.Audio.IsProcessed)
{
logger.LogInformation("Stream requested for track {TrackId} but audio is not ready", request.TrackId);
return Error.Conflict(description: "Track audio is not available for streaming yet.");
}
var response = await streamIssuer.IssueAsync(track.Id, track.FolderName, request.UserId, cancellationToken);
var response = await streamIssuer.IssueAsync(track.Id, track.Audio.FolderName, request.UserId, cancellationToken);
logger.LogInformation("Issued stream ticket for track {TrackId} to user {UserId}", request.TrackId, request.UserId);
@@ -25,7 +25,7 @@ namespace Musify.Application.Tracks.Responses
track.Source,
track.ExternalId,
track.Audio.TranscodeStatus,
track.Duration,
track.DurationSeconds,
listensCount,
track.OwnerUserId,
track.CreatedAt,