Separate responsibilities: start-listening command, API-side JSON converters, bulk track endpoints, mix kinds
This commit is contained in:
@@ -41,21 +41,6 @@ public class CreateTrackCommandHandler(
|
||||
}
|
||||
|
||||
var distinctTags = request.Tags.Distinct().ToList();
|
||||
if (distinctTags.Count == 0)
|
||||
{
|
||||
logger.LogWarning("Track {Title} was submitted without any tags", request.Title);
|
||||
return Error.Validation(description: "At least one tag is required.");
|
||||
}
|
||||
|
||||
var tagConflicts = GenreCompatibility.FindConflicts(distinctTags);
|
||||
if (tagConflicts.Count > 0)
|
||||
{
|
||||
var conflict = tagConflicts.First();
|
||||
logger.LogWarning(
|
||||
"Track {Title} was submitted with incompatible tags {First} and {Second}",
|
||||
request.Title, conflict.First, conflict.Second);
|
||||
return Error.Validation(description: $"Tags '{conflict.First}' and '{conflict.Second}' are not compatible.");
|
||||
}
|
||||
|
||||
if (request.PictureIntentId == request.AudioIntentId)
|
||||
return Error.Validation(description: "Picture and audio must use different upload intents.");
|
||||
@@ -105,11 +90,6 @@ public class CreateTrackCommandHandler(
|
||||
|
||||
await database.Tracks.AddAsync(trackEntity, cancellationToken);
|
||||
|
||||
await database.UserHasTracks.AddAsync(new UserHasTrack
|
||||
{
|
||||
UserId = request.UserId,
|
||||
TrackId = trackEntity.Id
|
||||
}, cancellationToken);
|
||||
|
||||
var sizes = trackConfiguration.PicturesSizes.ToImageSizes(trackConfiguration.Routes);
|
||||
await eventBus.PublishAsync(
|
||||
|
||||
@@ -17,9 +17,6 @@ public class RecordListeningProgressCommandHandler(IDatabase database)
|
||||
{
|
||||
public async ValueTask<ErrorOr<Success>> Handle(RecordListeningProgressCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!double.IsFinite(request.PlayedSeconds) || request.PlayedSeconds < 0)
|
||||
return Error.Validation(description: "playedSeconds must be a non-negative number.");
|
||||
|
||||
var listen = await database.ListeningHistories
|
||||
.Include(l => l.Track)
|
||||
.SingleOrDefaultAsync(l => l.Id == request.ListenId && l.UserId == request.UserId, cancellationToken);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Musify.Application.Serialization;
|
||||
using Musify.Domain.Entities;
|
||||
using Musify.Domain.ValueObjects;
|
||||
|
||||
@@ -14,7 +12,7 @@ public record TrackApplicationResponse(
|
||||
int ListensCount,
|
||||
DateTime CreatedAt,
|
||||
DateTime UpdatedAt,
|
||||
[property: JsonConverter(typeof(LongAsStringConverter))] long OwnerUserId,
|
||||
long OwnerUserId,
|
||||
IReadOnlyCollection<Genre> Tags,
|
||||
bool IsExplicit)
|
||||
{
|
||||
|
||||
+19
-11
@@ -5,24 +5,25 @@ using Microsoft.Extensions.Logging;
|
||||
using Musify.Application.Configuration;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Application.Services;
|
||||
using Musify.Application.Tracks.Responses;
|
||||
using Musify.Domain.ValueObjects;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Application.Tracks.Responses;
|
||||
using Musify.Domain.Entities;
|
||||
using Musify.Domain.ValueObjects;
|
||||
|
||||
namespace Musify.Application.Tracks;
|
||||
|
||||
/// <summary>Starts a listen: records it for signed-in users and issues the stream ticket.</summary>
|
||||
public record StartListeningCommand(Guid TrackId, long? UserId)
|
||||
: ICommand<ErrorOr<TrackStreamResponse>>;
|
||||
|
||||
public record GetTrackStreamQuery(Guid TrackId, long? UserId)
|
||||
: IQuery<ErrorOr<TrackStreamResponse>>;
|
||||
|
||||
public class GetTrackStreamQueryHandler(
|
||||
public class StartListeningCommandHandler(
|
||||
IDatabase database,
|
||||
TrackStreamIssuer streamIssuer,
|
||||
PlaybackConfiguration playbackConfiguration,
|
||||
ILogger<GetTrackStreamQueryHandler> logger)
|
||||
: IQueryHandler<GetTrackStreamQuery, ErrorOr<TrackStreamResponse>>
|
||||
ILogger<StartListeningCommandHandler> logger)
|
||||
: ICommandHandler<StartListeningCommand, ErrorOr<TrackStreamResponse>>
|
||||
{
|
||||
public async ValueTask<ErrorOr<TrackStreamResponse>> Handle(GetTrackStreamQuery request, CancellationToken cancellationToken)
|
||||
public async ValueTask<ErrorOr<TrackStreamResponse>> Handle(StartListeningCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.UserId == null && !playbackConfiguration.AllowAnonymousListening)
|
||||
return Error.Unauthorized(description: "Sign in to stream music, or ask an administrator to enable anonymous listening.");
|
||||
@@ -38,10 +39,17 @@ public class GetTrackStreamQueryHandler(
|
||||
if (!track.Audio.IsProcessed)
|
||||
return AppErrors.Conflict("Track.AudioNotReady", "Track audio is not available for streaming yet.");
|
||||
|
||||
var response = await streamIssuer.IssueAsync(track.Id, track.Audio.FolderName, request.UserId, cancellationToken);
|
||||
Guid? listenId = null;
|
||||
if (request.UserId is { } userId)
|
||||
{
|
||||
var listen = new ListeningHistory { UserId = userId, TrackId = track.Id };
|
||||
await database.ListeningHistories.AddAsync(listen, cancellationToken);
|
||||
await database.SaveChangesAsync(cancellationToken);
|
||||
listenId = listen.Id;
|
||||
}
|
||||
|
||||
logger.LogInformation("Issued stream ticket for track {TrackId} to user {UserId}", request.TrackId, request.UserId?.ToString() ?? "(anonymous)");
|
||||
|
||||
return response;
|
||||
return streamIssuer.Issue(track.Audio.FolderName, request.UserId, listenId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user