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,8 @@
using Ardalis.Result;
using Mediator;
namespace Musify.Application.PlayLists.Commands
{
public record AddTrackToPlayListCommand(long UserId, Guid PlayListId, Guid TrackId)
: ICommand<Result>;
}
@@ -0,0 +1,13 @@
using Ardalis.Result;
using Mediator;
using Musify.Application.PlayLists.Responses;
namespace Musify.Application.PlayLists.Commands
{
public record CreatePlayListCommand(
long UserId,
string Name,
string Description,
Guid? PictureIntentId)
: ICommand<Result<PlayListApplicationResponse>>;
}
@@ -0,0 +1,8 @@
using Ardalis.Result;
using Mediator;
namespace Musify.Application.PlayLists.Commands
{
public record DeletePlayListCommand(long UserId, Guid PlayListId)
: ICommand<Result>;
}
@@ -0,0 +1,8 @@
using Ardalis.Result;
using Mediator;
namespace Musify.Application.PlayLists.Commands
{
public record RemoveTrackFromPlayListCommand(long UserId, Guid PlayListId, Guid TrackId)
: ICommand<Result>;
}
@@ -0,0 +1,13 @@
using Ardalis.Result;
using Mediator;
using Musify.Application.PlayLists.Responses;
namespace Musify.Application.PlayLists.Commands
{
public record RequestPlayListPictureUploadCommand(
long UserId,
string FileType,
string ContentType,
long? ExpectedSizeBytes = null)
: ICommand<Result<PlayListPictureUploadResponse>>;
}
@@ -0,0 +1,14 @@
using Ardalis.Result;
using Mediator;
using Musify.Application.PlayLists.Responses;
namespace Musify.Application.PlayLists.Commands
{
public record UpdatePlayListCommand(
long UserId,
Guid PlayListId,
string? NewName,
string? NewDescription,
Guid? NewPictureIntentId)
: ICommand<Result<PlayListApplicationResponse>>;
}
@@ -0,0 +1,70 @@
using Ardalis.Result;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Musify.Application.Contracts;
using Musify.Application.PlayLists.Commands;
using Musify.Domain.Entities;
namespace Musify.Application.PlayLists.Handlers
{
public class AddTrackToPlayListCommandHandler(
IDatabase database,
ILogger<AddTrackToPlayListCommandHandler> logger)
: ICommandHandler<AddTrackToPlayListCommand, Result>
{
public async ValueTask<Result> Handle(AddTrackToPlayListCommand request, CancellationToken cancellationToken)
{
var playList = await database.PlayLists
.SingleOrDefaultAsync(pl => pl.Id == request.PlayListId, cancellationToken);
if (playList is null)
{
logger.LogInformation("Playlist {PlayListId} not found", request.PlayListId);
return Result.NotFound();
}
if (playList.UserId != request.UserId)
{
logger.LogWarning("User {UserId} is not the owner of playlist {PlayListId}", request.UserId, request.PlayListId);
return Result.Unauthorized();
}
var trackExists = await database.Tracks
.AsNoTracking()
.AnyAsync(t => t.Id == request.TrackId, cancellationToken);
if (!trackExists)
{
logger.LogInformation("Track {TrackId} not found", request.TrackId);
return Result.NotFound($"Track {request.TrackId} not found");
}
var alreadyAdded = await database.PlayListHasTracks
.AsNoTracking()
.AnyAsync(plt => plt.PlayListId == request.PlayListId && plt.TrackId == request.TrackId, cancellationToken);
if (alreadyAdded)
{
logger.LogInformation("Track {TrackId} already in playlist {PlayListId}", request.TrackId, request.PlayListId);
return Result.Conflict("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;
await database.PlayListHasTracks.AddAsync(new PlayListHasTrack
{
PlayListId = request.PlayListId,
TrackId = request.TrackId,
Position = nextPosition
}, cancellationToken);
await database.SaveChangesAsync(cancellationToken);
logger.LogInformation("Added track {TrackId} to playlist {PlayListId} at position {Position}",
request.TrackId, request.PlayListId, nextPosition);
return Result.Success();
}
}
}
@@ -0,0 +1,131 @@
using Ardalis.Result;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Musify.Application.Configuration;
using Musify.Application.Shared;
using Musify.Application.Events;
using Musify.Application.PlayLists.Commands;
using Musify.Application.PlayLists.Responses;
using Musify.Application.Extensions;
using Musify.Application.Services;
using Musify.Domain.Entities;
using Musify.Application.Contracts;
namespace Musify.Application.PlayLists.Handlers
{
public class CreatePlayListCommandHandler(
IEventBus eventBus,
IDatabase database,
UploadIntentValidator uploadIntentValidator,
ILogger<CreatePlayListCommandHandler> logger,
ApplicationStorageConfiguration storageConfiguration,
PlayListConfiguration playListConfiguration,
UploadIntentConfiguration uploadIntentConfiguration)
: ICommandHandler<CreatePlayListCommand, Result<PlayListApplicationResponse>>
{
public async ValueTask<Result<PlayListApplicationResponse>> Handle(CreatePlayListCommand request, CancellationToken cancellationToken)
{
var userExists = await database.Users
.AsNoTracking()
.AnyAsync(u => u.Id == request.UserId, cancellationToken);
if (!userExists)
{
logger.LogWarning("User {UserId} not found", request.UserId);
return Result.NotFound($"User {request.UserId} not found");
}
UploadIntent? pictureIntent = null;
string originalPictureName;
if (request.PictureIntentId.HasValue)
{
var validation = await uploadIntentValidator.ValidateAndLoadAsync(
uploadIntentConfiguration,
request.PictureIntentId.Value, request.UserId, cancellationToken);
if (!validation.IsSuccess)
return validation.As<UploadIntent, PlayListApplicationResponse>();
pictureIntent = validation.Value;
originalPictureName = pictureIntent.ObjectName;
}
else
{
originalPictureName = playListConfiguration.Routes.PresetOriginalPicture;
}
var playList = new PlayList
{
UserId = request.UserId,
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
};
await database.PlayLists.AddAsync(playList, cancellationToken);
if (pictureIntent is not null)
{
var finalPictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(request.UserId, originalPictureName);
var publishResult = await PublishCreatePlayListEventAsync(
playList.Id, pictureIntent, finalPictureKey, cancellationToken);
if (!publishResult.IsSuccess)
return publishResult;
}
try
{
await database.SaveChangesAsync(cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to save playlist for user {UserId}", request.UserId);
return Result.Error($"Failed to create playlist for user {request.UserId}");
}
logger.LogInformation("Created playlist {PlayListId} for user {UserId}", playList.Id, request.UserId);
return Result.Created(PlayListApplicationResponse.FromEntity(playList));
}
async Task<Result<PlayListApplicationResponse>> PublishCreatePlayListEventAsync(
Guid playListId,
UploadIntent pictureIntent,
string finalPictureKey,
CancellationToken cancellationToken)
{
try
{
await eventBus.PublishAsync(
new CreatePlayListResourcesEvent(
playListId,
pictureIntent.Id,
storageConfiguration.Bucket,
pictureIntent.Key,
finalPictureKey,
new ImageSize(
playListConfiguration.Routes.SmallPicturesPath,
playListConfiguration.PicturesSizes.SmallPictureWidth,
playListConfiguration.PicturesSizes.SmallPictureHeight),
new ImageSize(
playListConfiguration.Routes.MediumPicturesPath,
playListConfiguration.PicturesSizes.MediumPictureWidth,
playListConfiguration.PicturesSizes.MediumPictureHeight),
new ImageSize(
playListConfiguration.Routes.LargePicturesPath,
playListConfiguration.PicturesSizes.LargePictureWidth,
playListConfiguration.PicturesSizes.LargePictureHeight)),
cancellationToken);
return Result.Success();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to publish create playlist event for playlist {PlayListId}", playListId);
return Result.Error($"Failed to publish create playlist event for playlist {playListId}");
}
}
}
}
@@ -0,0 +1,60 @@
using Ardalis.Result;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Musify.Application.Contracts;
using Musify.Application.Events;
using Musify.Application.PlayLists.Commands;
using Musify.Domain.ValueObjects;
namespace Musify.Application.PlayLists.Handlers
{
public class DeletePlayListCommandHandler(
IDatabase database,
IEventBus eventBus,
ILogger<DeletePlayListCommandHandler> logger)
: ICommandHandler<DeletePlayListCommand, Result>
{
public async ValueTask<Result> Handle(DeletePlayListCommand request, CancellationToken cancellationToken)
{
var playList = await database.PlayLists.SingleOrDefaultAsync(p => p.Id == request.PlayListId, cancellationToken);
if (playList is null)
{
logger.LogDebug("Playlist {PlayListId} not found", request.PlayListId);
return Result.NotFound();
}
if (playList.UserId != request.UserId)
{
logger.LogWarning("Playlist {PlayListId} does not belong to user {UserId}", request.PlayListId, request.UserId);
return Result.Unauthorized();
}
playList.LifeCycleStatus = LifeCycleStatus.Removing;
try
{
await eventBus.PublishAsync(
new DeletePlayListEvent(playList.Id, request.UserId),
cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to publish delete playlist event for playlist {PlayListId}", request.PlayListId);
return Result.Error($"Failed to delete playlist {request.PlayListId}");
}
try
{
await database.SaveChangesAsync(cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to mark playlist {PlayListId} as removing", request.PlayListId);
return Result.Error($"Failed to delete playlist {request.PlayListId}");
}
return Result.NoContent();
}
}
}
@@ -0,0 +1,26 @@
using Ardalis.Result;
using Microsoft.EntityFrameworkCore;
using Musify.Application.PlayLists.Responses;
using Musify.Application.PlayLists.Queries;
using Mediator;
using Musify.Application.Contracts;
namespace Musify.Application.PlayLists.Handlers
{
public class GetPlayListByIdQueryHandler(IDatabase database)
: IQueryHandler<GetPlayListByIdQuery, Result<PlayListApplicationResponse>>
{
public async ValueTask<Result<PlayListApplicationResponse>> Handle(GetPlayListByIdQuery request, CancellationToken cancellationToken)
{
var playList = await database.PlayLists
.AsNoTracking()
.Where(p => p.Id == request.Id)
.Select(p => PlayListApplicationResponse.FromEntity(p))
.SingleOrDefaultAsync(cancellationToken);
return playList is null
? Result<PlayListApplicationResponse>.NotFound()
: Result.Success(playList);
}
}
}
@@ -0,0 +1,38 @@
using Ardalis.Result;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Musify.Application.Contracts;
using Musify.Application.Pagination;
using Musify.Application.PlayLists.Queries;
using Musify.Application.Tracks.Responses;
using X.PagedList.EF;
namespace Musify.Application.PlayLists.Handlers
{
public class GetPlayListTracksQueryHandler(IDatabase database)
: IQueryHandler<GetPlayListTracksQuery, Result<PaginatedResponse<TrackApplicationResponse>> >
{
public async ValueTask<Result<PaginatedResponse<TrackApplicationResponse> >> Handle(GetPlayListTracksQuery request, CancellationToken cancellationToken)
{
var playListExists = await database.PlayLists
.AsNoTracking()
.AnyAsync(p => p.Id == request.PlayListId, cancellationToken);
if (!playListExists)
return Result<PaginatedResponse<TrackApplicationResponse>>.NotFound();
var tracksQuery = database.PlayListHasTracks
.AsNoTracking()
.Include(plt => plt.Track)
.Where(plt => plt.PlayListId == request.PlayListId);
var totalCount = await tracksQuery.CountAsync(cancellationToken);
var pagedTracks = await tracksQuery
.OrderBy(plt => plt.Position)
.Select(plt => TrackApplicationResponse.FromEntity(plt.Track))
.ToPagedListAsync(request.PageNumber, request.PageSize, totalCount, cancellationToken);
return Result.Success(PaginatedResponse<TrackApplicationResponse>.FromPagedList(pagedTracks));
}
}
}
@@ -0,0 +1,37 @@
using Ardalis.Result;
using Microsoft.EntityFrameworkCore;
using Musify.Application.Pagination;
using Musify.Application.PlayLists.Responses;
using Musify.Application.PlayLists.Queries;
using X.PagedList.EF;
using Mediator;
using Musify.Application.Contracts;
namespace Musify.Application.PlayLists.Handlers
{
public class GetPlayListsByUserIdQueryHandler(IDatabase database)
: IQueryHandler<GetPlayListsByUserIdQuery, Result<PaginatedResponse<PlayListApplicationResponse> > >
{
public async ValueTask<Result<PaginatedResponse<PlayListApplicationResponse> >> Handle(GetPlayListsByUserIdQuery request, CancellationToken cancellationToken)
{
var playListsQuery = database.PlayLists
.AsNoTracking()
.Where(p => p.UserId == request.UserId);
if (request.Name is not null)
{
var normalizedName = request.Name.Trim().ToUpperInvariant();
playListsQuery = playListsQuery.Where(p => p.NormalizedName.Contains(normalizedName));
}
var totalCount = await playListsQuery.CountAsync(cancellationToken);
var pagedPlayLists = await playListsQuery
.OrderBy(p => p.CreatedAt)
.Select(p => PlayListApplicationResponse.FromEntity(p))
.ToPagedListAsync(request.PageNumber, request.PageSize, totalCount, cancellationToken);
return Result.Success(PaginatedResponse<PlayListApplicationResponse>.FromPagedList(pagedPlayLists));
}
}
}
@@ -0,0 +1,28 @@
using Ardalis.Result;
using Microsoft.EntityFrameworkCore;
using Musify.Application.Pagination;
using Musify.Application.PlayLists.Responses;
using Musify.Application.PlayLists.Queries;
using X.PagedList.EF;
using Mediator;
using Musify.Application.Contracts;
namespace Musify.Application.PlayLists.Handlers
{
public class GetPlayListsQueryHandler(IDatabase database)
: IQueryHandler<GetPlayListsQuery, Result<PaginatedResponse<PlayListApplicationResponse> >>
{
public async ValueTask<Result<PaginatedResponse<PlayListApplicationResponse> >> Handle(GetPlayListsQuery request, CancellationToken cancellationToken)
{
var totalCount = await database.PlayLists.CountAsync(cancellationToken);
var pagedPlayLists = await database.PlayLists
.AsNoTracking()
.OrderBy(p => p.Id)
.Select(p => PlayListApplicationResponse.FromEntity(p))
.ToPagedListAsync(request.PageNumber, request.PageSize, totalCount, cancellationToken);
return Result.Success(PaginatedResponse<PlayListApplicationResponse>.FromPagedList(pagedPlayLists));
}
}
}
@@ -0,0 +1,47 @@
using Ardalis.Result;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Musify.Application.Contracts;
using Musify.Application.PlayLists.Commands;
namespace Musify.Application.PlayLists.Handlers
{
public class RemoveTrackFromPlayListCommandHandler(
IDatabase database,
ILogger<RemoveTrackFromPlayListCommandHandler> logger)
: ICommandHandler<RemoveTrackFromPlayListCommand, Result>
{
public async ValueTask<Result> Handle(RemoveTrackFromPlayListCommand request, CancellationToken cancellationToken)
{
var playList = await database.PlayLists
.SingleOrDefaultAsync(pl => pl.Id == request.PlayListId, cancellationToken);
if (playList is null)
{
logger.LogInformation("Playlist {PlayListId} not found", request.PlayListId);
return Result.NotFound();
}
if (playList.UserId != request.UserId)
{
logger.LogWarning("User {UserId} is not the owner of playlist {PlayListId}", request.UserId, request.PlayListId);
return Result.Unauthorized();
}
var link = await database.PlayListHasTracks
.SingleOrDefaultAsync(plt => plt.PlayListId == request.PlayListId && plt.TrackId == request.TrackId, cancellationToken);
if (link is null)
{
logger.LogInformation("Track {TrackId} not in playlist {PlayListId}", request.TrackId, request.PlayListId);
return Result.NotFound("Track is not in the playlist.");
}
database.PlayListHasTracks.Remove(link);
await database.SaveChangesAsync(cancellationToken);
logger.LogInformation("Removed track {TrackId} from playlist {PlayListId}", request.TrackId, request.PlayListId);
return Result.Success();
}
}
}
@@ -0,0 +1,99 @@
using Ardalis.Result;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Musify.Application.Configuration;
using Musify.Application.PlayLists.Commands;
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.Handlers
{
public class RequestPlayListPictureUploadCommandHandler(
IDatabase database,
UploadIntentValidator uploadIntentValidator,
IStorageService storageService,
ILogger<RequestPlayListPictureUploadCommandHandler> logger,
ApplicationStorageConfiguration storageConfiguration,
PlayListConfiguration playListConfiguration,
UploadIntentConfiguration uploadIntentConfiguration)
: ICommandHandler<RequestPlayListPictureUploadCommand, Result<PlayListPictureUploadResponse>>
{
public async ValueTask<Result<PlayListPictureUploadResponse>> Handle(RequestPlayListPictureUploadCommand request, CancellationToken cancellationToken)
{
var userExists = await database.Users
.AsNoTracking()
.AnyAsync(u => u.Id == request.UserId, cancellationToken);
if (!userExists)
{
logger.LogWarning("User {UserId} not found", request.UserId);
return Result.NotFound($"User {request.UserId} not found");
}
var effectiveSizeBytes = request.ExpectedSizeBytes
?? uploadIntentConfiguration.DefaultExpectedPictureSizeBytes;
var objectName = $"{Guid.NewGuid() }.{request.FileType.TrimStart('.').ToLowerInvariant() }";
var tempKey = playListConfiguration.Routes.BuildTempPicturePath(
uploadIntentConfiguration.TemporalRootPrefix, request.UserId, objectName);
try
{
var uploadUrl = await storageService.GetUploadUrlAsync(
storageConfiguration.Bucket,
tempKey,
request.ContentType,
TimeSpan.FromSeconds(uploadIntentConfiguration.UploadUrlExpiresInSeconds),
cancellationToken);
await using var transaction = await database.BeginTransactionAsync(
System.Data.IsolationLevel.Serializable, cancellationToken);
var quotaCheck = await uploadIntentValidator.CheckQuotaAsync(
uploadIntentConfiguration,
request.UserId, effectiveSizeBytes, 1, cancellationToken);
if (!quotaCheck.IsSuccess)
{
logger.LogWarning("User {UserId} failed upload intent quota check", request.UserId);
return quotaCheck;
}
var intent = new UploadIntent
{
UserId = request.UserId,
Bucket = storageConfiguration.Bucket,
Key = tempKey,
ObjectName = objectName,
ContentType = request.ContentType,
ExpectedSizeBytes = effectiveSizeBytes,
Purpose = UploadIntentPurpose.PlayListPicture,
ExpiresAt = DateTime.UtcNow.AddSeconds(uploadIntentConfiguration.UploadUrlExpiresInSeconds),
};
await database.UploadIntents.AddAsync(intent, cancellationToken);
await database.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
logger.LogInformation("Issued upload intent {IntentId} for user {UserId} (PlayListPicture, temp key: {Key})", intent.Id, request.UserId, tempKey);
return Result.Success(new PlayListPictureUploadResponse(
intent.Id,
storageConfiguration.Bucket,
tempKey,
objectName,
request.ContentType,
uploadIntentConfiguration.UploadUrlExpiresInSeconds,
uploadUrl));
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to generate playlist picture upload URL for user {UserId}", request.UserId);
return Result.Error("Failed to generate upload URL");
}
}
}
}
@@ -0,0 +1,130 @@
using Ardalis.Result;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Musify.Application.Configuration;
using Musify.Application.Shared;
using Musify.Application.Events;
using Musify.Application.PlayLists.Commands;
using Musify.Application.PlayLists.Responses;
using Musify.Application.Extensions;
using Musify.Application.Services;
using Musify.Domain.Entities;
using Musify.Application.Contracts;
namespace Musify.Application.PlayLists.Handlers
{
public class UpdatePlayListCommandHandler(
IEventBus eventBus,
IDatabase database,
UploadIntentValidator uploadIntentValidator,
ILogger<UpdatePlayListCommandHandler> logger,
ApplicationStorageConfiguration storageConfiguration,
PlayListConfiguration playListConfiguration,
UploadIntentConfiguration uploadIntentConfiguration)
: ICommandHandler<UpdatePlayListCommand, Result<PlayListApplicationResponse>>
{
public async ValueTask<Result<PlayListApplicationResponse>> Handle(UpdatePlayListCommand request, CancellationToken cancellationToken)
{
var playListEntity = await database.PlayLists.SingleOrDefaultAsync(pl => pl.Id == request.PlayListId, cancellationToken);
if (playListEntity is null)
{
logger.LogInformation("Playlist {PlayListId} not found", request.PlayListId);
return Result.NotFound();
}
if (playListEntity.UserId != request.UserId)
{
logger.LogWarning("User {UserId} is not the owner of playlist {PlayListId}", request.UserId, request.PlayListId);
return Result.Unauthorized();
}
if (request.NewName is not null)
{
playListEntity.Name = request.NewName;
playListEntity.NormalizedName = request.NewName.Trim().ToUpperInvariant();
}
if (request.NewDescription is not null)
{
playListEntity.Description = request.NewDescription;
}
UploadIntent? pictureIntent = null;
string? finalPictureKey = null;
if (request.NewPictureIntentId.HasValue)
{
var validation = await uploadIntentValidator.ValidateAndLoadAsync(
uploadIntentConfiguration,
request.NewPictureIntentId.Value, request.UserId, cancellationToken);
if (!validation.IsSuccess)
return validation.As<UploadIntent, PlayListApplicationResponse>();
pictureIntent = validation.Value;
playListEntity.OriginalPictureName = pictureIntent.ObjectName;
finalPictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(request.UserId, pictureIntent.ObjectName);
}
database.PlayLists.Update(playListEntity);
if (pictureIntent is not null && finalPictureKey is not null)
{
var publishResult = await PublishUpdatePlayListPictureSourceEventAsync(
playListEntity.Id, pictureIntent, finalPictureKey, cancellationToken);
if (!publishResult.IsSuccess)
return publishResult;
}
try
{
await database.SaveChangesAsync(cancellationToken);
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to update playlist {PlayListId}", playListEntity.Id);
return Result.Error($"Failed to update playlist {playListEntity.Id}");
}
logger.LogInformation("Updated playlist {PlayListId}", playListEntity.Id);
return Result.Success(PlayListApplicationResponse.FromEntity(playListEntity));
}
async Task<Result<PlayListApplicationResponse>> PublishUpdatePlayListPictureSourceEventAsync(
Guid playListId,
UploadIntent pictureIntent,
string finalPictureKey,
CancellationToken cancellationToken)
{
try
{
await eventBus.PublishAsync(
new UpdatePlayListPictureSourceEvent(
playListId,
pictureIntent.Id,
storageConfiguration.Bucket,
pictureIntent.Key,
finalPictureKey,
new ImageSize(
playListConfiguration.Routes.SmallPicturesPath,
playListConfiguration.PicturesSizes.SmallPictureWidth,
playListConfiguration.PicturesSizes.SmallPictureHeight),
new ImageSize(
playListConfiguration.Routes.MediumPicturesPath,
playListConfiguration.PicturesSizes.MediumPictureWidth,
playListConfiguration.PicturesSizes.MediumPictureHeight),
new ImageSize(
playListConfiguration.Routes.LargePicturesPath,
playListConfiguration.PicturesSizes.LargePictureWidth,
playListConfiguration.PicturesSizes.LargePictureHeight)),
cancellationToken);
return Result.Success();
}
catch (Exception exception)
{
logger.LogError(exception, "Failed to publish update playlist picture source event for playlist {PlayListId}", playListId);
return Result.Error($"Failed to publish update playlist picture source event for playlist {playListId}");
}
}
}
}
@@ -0,0 +1,9 @@
using Ardalis.Result;
using Mediator;
using Musify.Application.PlayLists.Responses;
namespace Musify.Application.PlayLists.Queries
{
public record GetPlayListByIdQuery(Guid Id)
: IQuery<Result<PlayListApplicationResponse>>;
}
@@ -0,0 +1,13 @@
using Ardalis.Result;
using Mediator;
using Musify.Application.Pagination;
using Musify.Application.Tracks.Responses;
namespace Musify.Application.PlayLists.Queries
{
public record GetPlayListTracksQuery(
Guid PlayListId,
int PageNumber = 1,
int PageSize = 10)
: IQuery<Result<PaginatedResponse<TrackApplicationResponse>>>;
}
@@ -0,0 +1,14 @@
using Ardalis.Result;
using Mediator;
using Musify.Application.Pagination;
using Musify.Application.PlayLists.Responses;
namespace Musify.Application.PlayLists.Queries
{
public record GetPlayListsByUserIdQuery(
long UserId,
string? Name,
int PageNumber = 1,
int PageSize = 10)
: IQuery<Result<PaginatedResponse<PlayListApplicationResponse>> >;
}
@@ -0,0 +1,10 @@
using Ardalis.Result;
using Mediator;
using Musify.Application.Pagination;
using Musify.Application.PlayLists.Responses;
namespace Musify.Application.PlayLists.Queries
{
public record GetPlayListsQuery(int PageNumber = 1, int PageSize = 10)
: IQuery<Result<PaginatedResponse<PlayListApplicationResponse>> >;
}
@@ -0,0 +1,28 @@
using Musify.Domain.Entities;
namespace Musify.Application.PlayLists.Responses
{
public record PlayListApplicationResponse(
Guid Id,
string Name,
string Description,
string SmallImageKeyName,
string MediumImageKeyName,
string LargeImageKeyName,
DateTime CreatedAt,
DateTime UpdatedAt)
{
public static PlayListApplicationResponse FromEntity(PlayList playList)
{
return new PlayListApplicationResponse(
playList.Id,
playList.Name,
playList.Description,
playList.SmallPictureName,
playList.MediumPictureName,
playList.LargePictureName,
playList.CreatedAt,
playList.UpdatedAt);
}
}
}
@@ -0,0 +1,11 @@
namespace Musify.Application.PlayLists.Responses
{
public record PlayListPictureUploadResponse(
Guid IntentId,
string Bucket,
string Key,
string PictureName,
string ContentType,
int ExpiresInSeconds,
string UploadUrl);
}