This commit is contained in:
2026-07-18 23:29:57 +02:00
parent 4fed9bd059
commit 2dc544b2c0
109 changed files with 5134 additions and 2163 deletions
@@ -32,7 +32,7 @@ namespace Musify.Application.Tracks
{
public async ValueTask<ErrorOr<TrackApplicationResponse>> Handle(CreateTrackCommand request, CancellationToken cancellationToken)
{
var user = await database.Users.AsNoTracking()
var user = await database.Users
.SingleOrDefaultAsync(u => u.Id == request.UserId, cancellationToken);
if (user is null)
{
@@ -59,13 +59,12 @@ namespace Musify.Application.Tracks
var finalAudioKey = trackConfiguration.Routes.BuildOriginalAudioPath(request.UserId, audioIntent.ObjectName);
var audioProcessedFolderKey = trackConfiguration.Routes.BuildProcessedAudioPath(Guid.NewGuid().ToString());
var artist = await GetOrCreateUserArtistAsync(user, cancellationToken);
var trackEntity = new Track
var trackEntity = new LocalTrack
{
Title = request.Title,
NormalizedTitle = request.Title.ToUpperInvariant(),
OwnerUserId = user.Id,
Owner = user,
Pictures = new TrackPictures
{
OriginalName = pictureIntent.ObjectName,
@@ -81,14 +80,7 @@ namespace Musify.Application.Tracks
}
};
await database.Tracks.AddAsync(trackEntity, cancellationToken);
await database.TrackArtists.AddAsync(new TrackArtist
{
TrackId = trackEntity.Id,
ArtistId = artist.Id,
Position = 0
}, cancellationToken);
await database.LocalTracks.AddAsync(trackEntity, cancellationToken);
await database.UserHasTracks.AddAsync(new UserHasTrack
{
@@ -141,32 +133,5 @@ namespace Musify.Application.Tracks
return TrackApplicationResponse.FromEntity(trackEntity, listensCount: 0);
}
private async Task<Artist> GetOrCreateUserArtistAsync(User user, CancellationToken cancellationToken)
{
var existing = await database.Artists.SingleOrDefaultAsync(a => a.UserId == user.Id, cancellationToken);
if (existing is not null)
return existing;
var artist = new Artist
{
Name = user.Name,
NormalizedName = user.NormalizedName,
UserId = user.Id
};
await database.Artists.AddAsync(artist, cancellationToken);
try
{
await database.SaveChangesAsync(cancellationToken);
return artist;
}
catch (DbUpdateException)
{
database.Artists.Remove(artist);
return await database.Artists.SingleAsync(a => a.UserId == user.Id, cancellationToken);
}
}
}
}
@@ -2,6 +2,7 @@ using ErrorOr;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Musify.Domain.Entities;
using Musify.Domain.ValueObjects;
using Musify.Application.Events;
using Musify.Application.Contracts;
@@ -28,7 +29,7 @@ namespace Musify.Application.Tracks
return Error.NotFound();
}
if (track.OwnerUserId != request.UserId)
if (track is not LocalTrack localTrack || localTrack.OwnerUserId != request.UserId)
{
logger.LogWarning("User {UserId} unauthorized to delete track {TrackId}", request.UserId, request.TrackId);
return Error.Unauthorized();
@@ -2,6 +2,7 @@
using Microsoft.EntityFrameworkCore;
using Musify.Application.Contracts;
using Musify.Application.Tracks.Responses;
using Musify.Domain.Entities;
namespace Musify.Application.Tracks
{
@@ -24,8 +25,9 @@ namespace Musify.Application.Tracks
.ToListAsync(cancellationToken);
var tracksById = await database.Tracks
.Include(t => t.TrackArtists)
.Include(t => ((ExternalTrack)t).TrackArtists)
.ThenInclude(trackArtist => trackArtist.Artist)
.Include(t => ((LocalTrack)t).Owner)
.Where(t => recentTrackIds.Contains(t.Id))
.Select(t => new { t.Id, Track = t, ListensCount = t.ListeningHistories.Count } )
.ToDictionaryAsync(t => t.Id, cancellationToken);
@@ -3,6 +3,7 @@ using Mediator;
using Microsoft.EntityFrameworkCore;
using Musify.Application.Contracts;
using Musify.Application.Tracks.Responses;
using Musify.Domain.Entities;
namespace Musify.Application.Tracks
{
@@ -15,8 +16,9 @@ namespace Musify.Application.Tracks
public async ValueTask<ErrorOr<TrackApplicationResponse>> Handle(GetTrackByIdQuery request, CancellationToken cancellationToken)
{
var entity = await database.Tracks.AsNoTracking()
.Include(t => t.TrackArtists)
.Include(t => ((ExternalTrack)t).TrackArtists)
.ThenInclude(trackArtist => trackArtist.Artist)
.Include(t => ((LocalTrack)t).Owner)
.Where(t => t.Id == request.TrackId)
.Select(t => new { Track = t, ListensCount = t.ListeningHistories.Count })
.SingleOrDefaultAsync(cancellationToken);
@@ -6,6 +6,7 @@ using Musify.Application.Tracks.Responses;
using X.PagedList;
using X.PagedList.EF;
using Musify.Application.Contracts;
using Musify.Domain.Entities;
namespace Musify.Application.Tracks
{
@@ -19,9 +20,9 @@ namespace Musify.Application.Tracks
{
var tracksQuery = database.UserHasTracks
.AsNoTracking()
.Include(ut => ut.Track)
.ThenInclude(track => track.TrackArtists)
.Include(ut => ((ExternalTrack)ut.Track).TrackArtists)
.ThenInclude(trackArtist => trackArtist.Artist)
.Include(ut => ((LocalTrack)ut.Track).Owner)
.AsQueryable();
if (!string.IsNullOrEmpty(request.Name))
@@ -6,6 +6,7 @@ using Musify.Application.Tracks.Responses;
using X.PagedList;
using X.PagedList.EF;
using Musify.Application.Contracts;
using Musify.Domain.Entities;
namespace Musify.Application.Tracks
{
@@ -26,9 +27,9 @@ namespace Musify.Application.Tracks
var tracksQuery = database.UserHasTracks
.AsNoTracking()
.Include(ut => ut.Track)
.ThenInclude(track => track.TrackArtists)
.Include(ut => ((ExternalTrack)ut.Track).TrackArtists)
.ThenInclude(trackArtist => trackArtist.Artist)
.Include(ut => ((LocalTrack)ut.Track).Owner)
.Where(p => p.UserId == request.UserId);
if (!string.IsNullOrEmpty(request.Name))
@@ -22,23 +22,23 @@ namespace Musify.Application.Tracks.Responses
track.Id,
track.Title,
FormatArtist(track),
track.Source,
track.ExternalId,
track is ExternalTrack external ? external.Source : TrackSource.Local,
(track as ExternalTrack)?.ExternalId,
track.Audio.TranscodeStatus,
track.DurationSeconds,
listensCount,
track.OwnerUserId,
(track as LocalTrack)?.OwnerUserId,
track.CreatedAt,
track.UpdatedAt);
private static string? FormatArtist(Track track)
private static string? FormatArtist(Track track) => track switch
{
if (track.TrackArtists.Count == 0)
return null;
return string.Join(", ", track.TrackArtists
.OrderBy(trackArtist => trackArtist.Position)
.Select(trackArtist => trackArtist.Artist.Name));
}
LocalTrack local => local.Owner.Name,
ExternalTrack { TrackArtists.Count: > 0 } external =>
string.Join(", ", external.TrackArtists
.OrderBy(trackArtist => trackArtist.Position)
.Select(trackArtist => trackArtist.Artist.Name)),
_ => null
};
}
}