diff --git a/backend/.editorconfig b/backend/.editorconfig index 8af7303..bcc34a2 100644 --- a/backend/.editorconfig +++ b/backend/.editorconfig @@ -41,6 +41,13 @@ dotnet_naming_symbols.private_fields.applicable_accessibilities = private dotnet_naming_symbols.private_fields.required_modifiers = dotnet_naming_style.camel_case.capitalization = camel_case +dotnet_naming_rule.private_static_readonly.symbols = private_static_readonly +dotnet_naming_rule.private_static_readonly.style = pascal_case +dotnet_naming_rule.private_static_readonly.severity = warning +dotnet_naming_symbols.private_static_readonly.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly.required_modifiers = static, readonly + dotnet_naming_rule.private_constants.symbols = private_constants dotnet_naming_rule.private_constants.style = pascal_case dotnet_naming_rule.private_constants.severity = warning @@ -48,18 +55,16 @@ dotnet_naming_symbols.private_constants.applicable_kinds = field dotnet_naming_symbols.private_constants.required_modifiers = const dotnet_naming_style.pascal_case.capitalization = pascal_case -# Analyzer rules that would need behaviour or API changes; downgraded in P0, revisit in P15. +# CA1848/CA1873 would turn every log call into a [LoggerMessage] source-generated method; CA1716 flags the +# EF-style IDatabase.Set() and the Musify.Application.Shared namespace, whose names are deliberate. dotnet_diagnostic.CA1848.severity = suggestion dotnet_diagnostic.CA1873.severity = suggestion -dotnet_diagnostic.CA1305.severity = suggestion -dotnet_diagnostic.CA1707.severity = suggestion -dotnet_diagnostic.CA1859.severity = suggestion dotnet_diagnostic.CA1716.severity = suggestion -dotnet_diagnostic.CA1000.severity = suggestion -dotnet_diagnostic.CA1725.severity = suggestion -dotnet_diagnostic.CA1816.severity = suggestion +# The routing slip builders are stateless services resolved from DI; they stay instance members. dotnet_diagnostic.CA1822.severity = suggestion -dotnet_diagnostic.CA1852.severity = suggestion -dotnet_diagnostic.CA1068.severity = suggestion -dotnet_diagnostic.CA1711.severity = suggestion -dotnet_diagnostic.CA1861.severity = suggestion + +# Test names follow Method_Scenario_Expected. +[Tests/**.cs] +dotnet_diagnostic.CA1707.severity = none +# xUnit collection fixtures are conventionally named ...Collection. +dotnet_diagnostic.CA1711.severity = none diff --git a/backend/Core/Musify.Application/Configuration/Pictures.cs b/backend/Core/Musify.Application/Configuration/Pictures.cs index bed36ff..5e71035 100644 --- a/backend/Core/Musify.Application/Configuration/Pictures.cs +++ b/backend/Core/Musify.Application/Configuration/Pictures.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Globalization; using Musify.Application.Shared; namespace Musify.Application.Configuration; @@ -41,10 +42,10 @@ public class PictureRoutes public string BuildPicturePath(PictureSize size, string pictureName) => StorageKey.Combine(FolderPath(size), pictureName); public string BuildOriginalPicturePath(long userId, string pictureName) => - StorageKey.Combine(UploadsFolder, userId.ToString(), ParentFolder, OriginalPicturesFolder, pictureName); + StorageKey.Combine(UploadsFolder, userId.ToString(CultureInfo.InvariantCulture), ParentFolder, OriginalPicturesFolder, pictureName); public string BuildTempPath(string tempRootPrefix, long userId, string objectName) => - StorageKey.Combine(tempRootPrefix, userId.ToString(), ParentFolder, objectName); + StorageKey.Combine(tempRootPrefix, userId.ToString(CultureInfo.InvariantCulture), ParentFolder, objectName); } public class PictureSizes diff --git a/backend/Core/Musify.Application/Configuration/TrackConfiguration.cs b/backend/Core/Musify.Application/Configuration/TrackConfiguration.cs index b391286..80b8df3 100644 --- a/backend/Core/Musify.Application/Configuration/TrackConfiguration.cs +++ b/backend/Core/Musify.Application/Configuration/TrackConfiguration.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Globalization; using Musify.Application.Shared; namespace Musify.Application.Configuration; @@ -48,7 +49,7 @@ public class TrackRoutes : PictureRoutes public string PresetLargePicturePath => StorageKey.Combine(ParentFolder, PresetLargePicture); public string BuildOriginalAudioPath(long userId, string audioName) => - StorageKey.Combine(UploadsFolder, userId.ToString(), ParentFolder, OriginalAudiosFolder, audioName); + StorageKey.Combine(UploadsFolder, userId.ToString(CultureInfo.InvariantCulture), ParentFolder, OriginalAudiosFolder, audioName); public string BuildProcessedAudioPath(string folderName) => StorageKey.Combine(ParentFolder, ProcessedAudiosFolder, folderName); diff --git a/backend/Core/Musify.Application/Genres/GetAvailableGenres.cs b/backend/Core/Musify.Application/Genres/GetAvailableGenres.cs index cffdd5f..694c9cd 100644 --- a/backend/Core/Musify.Application/Genres/GetAvailableGenres.cs +++ b/backend/Core/Musify.Application/Genres/GetAvailableGenres.cs @@ -1,6 +1,6 @@ using Mediator; -using Musify.Domain.ValueObjects; using Musify.Application.Genres.Responses; +using Musify.Domain.ValueObjects; namespace Musify.Application.Genres; diff --git a/backend/Core/Musify.Application/Genres/GetGenres.cs b/backend/Core/Musify.Application/Genres/GetGenres.cs index 396db60..6ad2465 100644 --- a/backend/Core/Musify.Application/Genres/GetGenres.cs +++ b/backend/Core/Musify.Application/Genres/GetGenres.cs @@ -1,8 +1,8 @@ using Mediator; using Microsoft.EntityFrameworkCore; using Musify.Application.Contracts; -using Musify.Domain.ValueObjects; using Musify.Application.Genres.Responses; +using Musify.Domain.ValueObjects; namespace Musify.Application.Genres; diff --git a/backend/Core/Musify.Application/Mixes/GenerateMixesForUser.cs b/backend/Core/Musify.Application/Mixes/GenerateMixesForUser.cs index e5c6a26..0bf792f 100644 --- a/backend/Core/Musify.Application/Mixes/GenerateMixesForUser.cs +++ b/backend/Core/Musify.Application/Mixes/GenerateMixesForUser.cs @@ -79,7 +79,7 @@ public class GenerateMixesForUserCommandHandler( private static List Shuffle(IEnumerable trackIds) => [.. trackIds.OrderBy(_ => Guid.NewGuid())]; - private static List Interleave(IReadOnlyList first, IReadOnlyList second) + private static List Interleave(List first, List second) { var merged = new List(first.Count + second.Count); for (var index = 0; index < Math.Max(first.Count, second.Count); index++) diff --git a/backend/Core/Musify.Application/Tracks/CreateTrack.cs b/backend/Core/Musify.Application/Tracks/CreateTrack.cs index 6c42b75..52ec80d 100644 --- a/backend/Core/Musify.Application/Tracks/CreateTrack.cs +++ b/backend/Core/Musify.Application/Tracks/CreateTrack.cs @@ -4,9 +4,9 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Musify.Application.Configuration; using Musify.Application.Contracts; -using Musify.Application.Shared; using Musify.Application.Events; using Musify.Application.Services; +using Musify.Application.Shared; using Musify.Application.Tracks.Responses; using Musify.Domain.Entities; using Musify.Domain.ValueObjects; diff --git a/backend/Core/Musify.Application/Tracks/StartListening.cs b/backend/Core/Musify.Application/Tracks/StartListening.cs index d580698..ef3aeb1 100644 --- a/backend/Core/Musify.Application/Tracks/StartListening.cs +++ b/backend/Core/Musify.Application/Tracks/StartListening.cs @@ -1,3 +1,4 @@ +using System.Globalization; using ErrorOr; using Mediator; using Microsoft.EntityFrameworkCore; @@ -48,7 +49,7 @@ public class StartListeningCommandHandler( listenId = listen.Id; } - logger.LogInformation("Issued stream ticket for track {TrackId} to user {UserId}", request.TrackId, request.UserId?.ToString() ?? "(anonymous)"); + logger.LogInformation("Issued stream ticket for track {TrackId} to user {UserId}", request.TrackId, request.UserId?.ToString(CultureInfo.InvariantCulture) ?? "(anonymous)"); return streamIssuer.Issue(track.Audio.FolderName, request.UserId, listenId); } diff --git a/backend/Core/Musify.Application/Users/GetUserById.cs b/backend/Core/Musify.Application/Users/GetUserById.cs index 3069355..0d6eab3 100644 --- a/backend/Core/Musify.Application/Users/GetUserById.cs +++ b/backend/Core/Musify.Application/Users/GetUserById.cs @@ -2,8 +2,8 @@ using ErrorOr; using Mediator; using Microsoft.EntityFrameworkCore; using Musify.Application.Contracts; -using Musify.Application.Users.Responses; using Musify.Application.Shared; +using Musify.Application.Users.Responses; namespace Musify.Application.Users; diff --git a/backend/Core/Musify.Domain/Entities/Mix.cs b/backend/Core/Musify.Domain/Entities/Mix.cs index 52df127..4951abe 100644 --- a/backend/Core/Musify.Domain/Entities/Mix.cs +++ b/backend/Core/Musify.Domain/Entities/Mix.cs @@ -1,8 +1,7 @@ using System.ComponentModel.DataAnnotations; -using Musify.Domain.ValueObjects; using System.ComponentModel.DataAnnotations.Schema; - using Musify.Domain.Abstractions; +using Musify.Domain.ValueObjects; namespace Musify.Domain.Entities; diff --git a/backend/Core/Musify.Domain/ValueObjects/TrackAudio.cs b/backend/Core/Musify.Domain/ValueObjects/TrackAudio.cs index a558d13..8ed8584 100644 --- a/backend/Core/Musify.Domain/ValueObjects/TrackAudio.cs +++ b/backend/Core/Musify.Domain/ValueObjects/TrackAudio.cs @@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis; namespace Musify.Domain.ValueObjects; -#pragma warning disable CS8618 public class TrackAudio { public string? OriginalName { get; set; } diff --git a/backend/Core/Musify.Domain/ValueObjects/TrackPictures.cs b/backend/Core/Musify.Domain/ValueObjects/TrackPictures.cs index 511f658..05a57d8 100644 --- a/backend/Core/Musify.Domain/ValueObjects/TrackPictures.cs +++ b/backend/Core/Musify.Domain/ValueObjects/TrackPictures.cs @@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis; namespace Musify.Domain.ValueObjects; -#pragma warning disable CS8618 public class TrackPictures { public string? OriginalName { get; set; } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/CopyFileInBucketActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/CopyFileInBucketActivity.cs index b6e4861..5662355 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/CopyFileInBucketActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/CopyFileInBucketActivity.cs @@ -15,28 +15,16 @@ internal class CopyFileInBucketActivity( public async Task Execute(ExecuteContext executeContext) { - try - { - await storageService.CopyFileAsync( - executeContext.Arguments.SourceBucket, - executeContext.Arguments.SourceKey, - executeContext.Arguments.DestinationBucket, - executeContext.Arguments.DestinationKey, - executeContext.CancellationToken); + await storageService.CopyFileAsync( + executeContext.Arguments.SourceBucket, + executeContext.Arguments.SourceKey, + executeContext.Arguments.DestinationBucket, + executeContext.Arguments.DestinationKey, + executeContext.CancellationToken); - return executeContext.Completed(new CopyFileInBucketLog( - executeContext.Arguments.DestinationBucket, - executeContext.Arguments.DestinationKey)); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to copy {SourceBucket}/{SourceKey} to {DestinationBucket}/{DestinationKey}", - executeContext.Arguments.SourceBucket, - executeContext.Arguments.SourceKey, - executeContext.Arguments.DestinationBucket, - executeContext.Arguments.DestinationKey); - throw; - } + return executeContext.Completed(new CopyFileInBucketLog( + executeContext.Arguments.DestinationBucket, + executeContext.Arguments.DestinationKey)); } public async Task Compensate(CompensateContext compensateContext) diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs index 6b246a5..3faab94 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/DownloadFileFromBucketActivity.cs @@ -18,33 +18,25 @@ internal class DownloadFileFromBucketActivity( var destinationPath = executeContext.GetVariable(executeContext.Arguments.DestinationFilePathVariable); ArgumentNullException.ThrowIfNull(destinationPath); - try - { - using var fileStream = await storageService.GetFileAsync( - executeContext.Arguments.Bucket, - executeContext.Arguments.Key, - executeContext.CancellationToken) - ?? throw new FileNotFoundException($"Object {executeContext.Arguments.Bucket}/{executeContext.Arguments.Key} was not found."); + using var fileStream = await storageService.GetFileAsync( + executeContext.Arguments.Bucket, + executeContext.Arguments.Key, + executeContext.CancellationToken) + ?? throw new FileNotFoundException($"Object {executeContext.Arguments.Bucket}/{executeContext.Arguments.Key} was not found."); - var destinationDirectory = Path.GetDirectoryName(destinationPath); - if (!string.IsNullOrWhiteSpace(destinationDirectory)) - Directory.CreateDirectory(destinationDirectory); + var destinationDirectory = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrWhiteSpace(destinationDirectory)) + Directory.CreateDirectory(destinationDirectory); - if (fileStream.CanSeek) - fileStream.Position = 0; + if (fileStream.CanSeek) + fileStream.Position = 0; - using var destinationStream = File.Create(destinationPath); - await fileStream.CopyToAsync(destinationStream, executeContext.CancellationToken); - return executeContext.Completed(new DownloadFileFromBucketLog( - executeContext.Arguments.Bucket, - executeContext.Arguments.Key, - destinationPath)); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to download file from bucket"); - throw; - } + using var destinationStream = File.Create(destinationPath); + await fileStream.CopyToAsync(destinationStream, executeContext.CancellationToken); + return executeContext.Completed(new DownloadFileFromBucketLog( + executeContext.Arguments.Bucket, + executeContext.Arguments.Key, + destinationPath)); } public Task Compensate(CompensateContext compensateContext) diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs index 73c737b..e5fce5d 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/RemoveFileFromBucketActivity.cs @@ -34,12 +34,5 @@ internal class RemoveFileFromBucketActivity( executeContext.Arguments.Bucket); return executeContext.Completed(); } - catch (Exception exception) - { - logger.LogError(exception, "Failed to remove {Key} from bucket {Bucket}", - executeContext.Arguments.Key, - executeContext.Arguments.Bucket); - throw; - } } } diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs index eb397d8..7b5f310 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/TransferFilesToBucketActivity.cs @@ -20,25 +20,15 @@ internal class TransferFilesToBucketActivity( var destinationKey = executeContext.Arguments.DestinationKey; ArgumentNullException.ThrowIfNull(destinationKey); - try - { - var uploadedKeys = await storageService.TransferFilesAsync( - folderPath, - executeContext.Arguments.DestinationBucket, - destinationKey, - executeContext.CancellationToken); + var uploadedKeys = await storageService.TransferFilesAsync( + folderPath, + executeContext.Arguments.DestinationBucket, + destinationKey, + executeContext.CancellationToken); - return executeContext.Completed(new TransferFilesToBucketLog( - executeContext.Arguments.DestinationBucket, - uploadedKeys.ToArray())); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to transfer files from {FolderPath} to {DestinationBucket}", - folderPath, - executeContext.Arguments.DestinationBucket); - throw; - } + return executeContext.Completed(new TransferFilesToBucketLog( + executeContext.Arguments.DestinationBucket, + uploadedKeys.ToArray())); } public async Task Compensate(CompensateContext compensateContext) diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs index c9adc6f..f66d1db 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Files/UploadFileToBucketActivity.cs @@ -21,22 +21,14 @@ internal class UploadFileToBucketActivity(IStorageService storageService, .Where(static s => !string.IsNullOrWhiteSpace(s)) .Select(static s => s.Trim().Trim('/', '\\'))); - try - { - await storageService.UploadFileAsync( - sourceFilePath, - executeContext.Arguments.DestinationBucket, - destinationKey, - executeContext.CancellationToken); - return executeContext.CompletedWithVariables(new UploadFileToBucketLog( - executeContext.Arguments.DestinationBucket, - destinationKey)); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to upload file to bucket"); - throw; - } + await storageService.UploadFileAsync( + sourceFilePath, + executeContext.Arguments.DestinationBucket, + destinationKey, + executeContext.CancellationToken); + return executeContext.CompletedWithVariables(new UploadFileToBucketLog( + executeContext.Arguments.DestinationBucket, + destinationKey)); } public async Task Compensate(CompensateContext compensateContext) diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs index f3614ba..94c7758 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Activities/Pictures/ResizePictureActivity.cs @@ -20,39 +20,31 @@ internal class ResizePictureActivity( var destinationFilePath = executeContext.GetVariable(executeContext.Arguments.DestinationFilePathVariable); ArgumentNullException.ThrowIfNull(destinationFilePath); - try + if (!File.Exists(sourceFilePath)) { - if (!File.Exists(sourceFilePath)) - { - logger.LogWarning("Source file not found: {SourceFilePath}", sourceFilePath); - throw new FileNotFoundException($"Source file not found: {sourceFilePath}"); - } - - await using var fileStream = File.OpenRead(sourceFilePath); - - await using var resizedPicture = await pictureHandler.ResizePictureAsWebpAsync( - fileStream, - executeContext.Arguments.Width, - executeContext.Arguments.Height, - executeContext.CancellationToken); - - var destinationDirectory = Path.GetDirectoryName(destinationFilePath); - if (!string.IsNullOrEmpty(destinationDirectory)) - Directory.CreateDirectory(destinationDirectory); - - await using var destinationStream = File.Create(destinationFilePath); - await resizedPicture.CopyToAsync(destinationStream, executeContext.CancellationToken); - - logger.LogDebug("Resized picture from {SourceFilePath} to {DestinationFilePath}", - sourceFilePath, - destinationFilePath); - return executeContext.Completed(new ResizePictureLog(destinationFilePath)); - } - catch (Exception exception) - { - logger.LogError(exception, "Failed to resize picture {SourceFilePath}", sourceFilePath); - throw; + logger.LogWarning("Source file not found: {SourceFilePath}", sourceFilePath); + throw new FileNotFoundException($"Source file not found: {sourceFilePath}"); } + + await using var fileStream = File.OpenRead(sourceFilePath); + + await using var resizedPicture = await pictureHandler.ResizePictureAsWebpAsync( + fileStream, + executeContext.Arguments.Width, + executeContext.Arguments.Height, + executeContext.CancellationToken); + + var destinationDirectory = Path.GetDirectoryName(destinationFilePath); + if (!string.IsNullOrEmpty(destinationDirectory)) + Directory.CreateDirectory(destinationDirectory); + + await using var destinationStream = File.Create(destinationFilePath); + await resizedPicture.CopyToAsync(destinationStream, executeContext.CancellationToken); + + logger.LogDebug("Resized picture from {SourceFilePath} to {DestinationFilePath}", + sourceFilePath, + destinationFilePath); + return executeContext.Completed(new ResizePictureLog(destinationFilePath)); } public Task Compensate(CompensateContext compensateContext) diff --git a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/RoutingSlipCleanUpConsumer.cs b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/RoutingSlipCleanUpConsumer.cs index 870e010..0239bd4 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/Consumers/RoutingSlipCleanUpConsumer.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/Consumers/RoutingSlipCleanUpConsumer.cs @@ -12,11 +12,11 @@ public class RoutingSlipCleanUpConsumer( { public const string QueueName = "routing-slip-clean-up"; - public Task Consume(ConsumeContext consumeContext) - => DeleteFolder(consumeContext.Message.Variables); + public Task Consume(ConsumeContext context) + => DeleteFolder(context.Message.Variables); - public Task Consume(ConsumeContext consumeContext) - => DeleteFolder(consumeContext.Message.Variables); + public Task Consume(ConsumeContext context) + => DeleteFolder(context.Message.Variables); private Task DeleteFolder(IDictionary variables) { diff --git a/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.Registration.cs b/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.Registration.cs index b039d5a..0053b0d 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.Registration.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.Registration.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Musify.Infrastructure.MassTransit.Activities.Albums; using Musify.Infrastructure.MassTransit.Activities.Audio; using Musify.Infrastructure.MassTransit.Activities.Files; +using Musify.Infrastructure.MassTransit.Activities.LifeCycle; using Musify.Infrastructure.MassTransit.Activities.Pictures; using Musify.Infrastructure.MassTransit.Activities.PlayLists; using Musify.Infrastructure.MassTransit.Activities.Tracks; @@ -11,7 +12,6 @@ using Musify.Infrastructure.MassTransit.Arguments; using Musify.Infrastructure.MassTransit.Consumers; using Musify.Infrastructure.MassTransit.Logs; using Musify.Infrastructure.MassTransit.RoutingSlip.Builders; -using Musify.Infrastructure.MassTransit.Activities.LifeCycle; namespace Musify.Infrastructure.MassTransit; diff --git a/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs b/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs index f5ec69a..3ddf79f 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs @@ -1,11 +1,11 @@ using MassTransit; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Musify.Application.Configuration; using Musify.Application.Contracts; using Musify.Infrastructure.Configuration; using Musify.Infrastructure.MassTransit.Sagas; using Musify.Infrastructure.Persistence; -using Musify.Application.Configuration; namespace Musify.Infrastructure.MassTransit; diff --git a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteRoutingSlipBuilder.cs b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteRoutingSlipBuilder.cs index b13d252..694c7ff 100644 --- a/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteRoutingSlipBuilder.cs +++ b/backend/Core/Musify.Infrastructure/MassTransit/RoutingSlip/Builders/DeleteRoutingSlipBuilder.cs @@ -3,8 +3,8 @@ using Microsoft.EntityFrameworkCore; using Musify.Application.Configuration; using Musify.Application.Contracts; using Musify.Domain.ValueObjects; -using Musify.Infrastructure.MassTransit.Arguments; using Musify.Infrastructure.MassTransit.Activities.LifeCycle; +using Musify.Infrastructure.MassTransit.Arguments; namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders; diff --git a/backend/Core/Musify.Infrastructure/Observability/OpenTelemetryDependencyInjection.cs b/backend/Core/Musify.Infrastructure/Observability/OpenTelemetryDependencyInjection.cs index 83a513f..2178791 100644 --- a/backend/Core/Musify.Infrastructure/Observability/OpenTelemetryDependencyInjection.cs +++ b/backend/Core/Musify.Infrastructure/Observability/OpenTelemetryDependencyInjection.cs @@ -1,12 +1,12 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Musify.Application.Configuration; using Musify.Infrastructure.Configuration; using OpenTelemetry.Exporter; using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; -using Musify.Application.Configuration; namespace Musify.Infrastructure.Observability; diff --git a/backend/Core/Musify.Infrastructure/Persistence/DatabaseDependencyInjection.cs b/backend/Core/Musify.Infrastructure/Persistence/DatabaseDependencyInjection.cs index c21b070..9ba10fe 100644 --- a/backend/Core/Musify.Infrastructure/Persistence/DatabaseDependencyInjection.cs +++ b/backend/Core/Musify.Infrastructure/Persistence/DatabaseDependencyInjection.cs @@ -1,8 +1,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Musify.Application.Configuration; using Musify.Application.Contracts; using Musify.Infrastructure.Configuration; -using Musify.Application.Configuration; namespace Musify.Infrastructure.Persistence; diff --git a/backend/Core/Musify.Infrastructure/Services/StreamTicketService.cs b/backend/Core/Musify.Infrastructure/Services/StreamTicketService.cs index f5c8d17..f52e92a 100644 --- a/backend/Core/Musify.Infrastructure/Services/StreamTicketService.cs +++ b/backend/Core/Musify.Infrastructure/Services/StreamTicketService.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Security.Cryptography; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; @@ -30,9 +31,9 @@ public sealed class StreamTicketService : IStreamTicketService, IDisposable var claims = new Dictionary { ["prefix"] = keyPrefix }; if (userId != null) - claims["sub"] = userId.Value.ToString(); + claims["sub"] = userId.Value.ToString(CultureInfo.InvariantCulture); if (maxBytes != null) - claims["maxBytes"] = maxBytes.Value.ToString(); + claims["maxBytes"] = maxBytes.Value.ToString(CultureInfo.InvariantCulture); var descriptor = new SecurityTokenDescriptor { diff --git a/backend/Hosts/Musify.Api/Authentication/JwtBearerEventsHandler.cs b/backend/Hosts/Musify.Api/Authentication/JwtBearerEventsHandler.cs index 6f65216..2a705c9 100644 --- a/backend/Hosts/Musify.Api/Authentication/JwtBearerEventsHandler.cs +++ b/backend/Hosts/Musify.Api/Authentication/JwtBearerEventsHandler.cs @@ -26,9 +26,9 @@ public sealed class JwtBearerEventsHandler( [property: JsonPropertyName("family_name")] string? FamilyName, [property: JsonPropertyName("picture")] string? Picture); - public override async Task TokenValidated(TokenValidatedContext tokenValidatedContext) + public override async Task TokenValidated(TokenValidatedContext context) { - var principal = tokenValidatedContext.Principal!; + var principal = context.Principal!; var rawId = principal.FindFirstValue(ClaimTypes.NameIdentifier); if (!long.TryParse(rawId, out var userId)) @@ -41,7 +41,7 @@ public sealed class JwtBearerEventsHandler( if (cache.TryGetValue(cacheKey, out _)) return; - var userInfo = await FetchUserInfoAsync(tokenValidatedContext, tokenValidatedContext.HttpContext.RequestAborted); + var userInfo = await FetchUserInfoAsync(context, context.HttpContext.RequestAborted); var username = userInfo?.Name ?? userInfo?.PreferredUsername @@ -65,7 +65,7 @@ public sealed class JwtBearerEventsHandler( { await mediator.Send( new SyncUserCommand(userId, username, firstName, lastName, profilePictureUrl), - tokenValidatedContext.HttpContext.RequestAborted); + context.HttpContext.RequestAborted); cache.Set(cacheKey, true, UserSyncCacheTtl); } @@ -76,9 +76,9 @@ public sealed class JwtBearerEventsHandler( } private async Task FetchUserInfoAsync( - TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken) + TokenValidatedContext context, CancellationToken cancellationToken) { - var authorizationHeader = tokenValidatedContext.HttpContext.Request.Headers.Authorization.ToString(); + var authorizationHeader = context.HttpContext.Request.Headers.Authorization.ToString(); if (!authorizationHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) return null; @@ -88,7 +88,7 @@ public sealed class JwtBearerEventsHandler( try { - var userInfoEndpoint = await ResolveUserInfoEndpointAsync(tokenValidatedContext, cancellationToken); + var userInfoEndpoint = await ResolveUserInfoEndpointAsync(context, cancellationToken); var client = httpClientFactory.CreateClient(); using var request = new HttpRequestMessage(HttpMethod.Get, userInfoEndpoint) @@ -113,9 +113,9 @@ public sealed class JwtBearerEventsHandler( } private async Task ResolveUserInfoEndpointAsync( - TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken) + TokenValidatedContext context, CancellationToken cancellationToken) { - var configurationManager = tokenValidatedContext.Options.ConfigurationManager; + var configurationManager = context.Options.ConfigurationManager; if (configurationManager != null) { var configuration = await configurationManager.GetConfigurationAsync(cancellationToken); diff --git a/backend/Hosts/Musify.Api/Endpoints/AlbumEndpoints.cs b/backend/Hosts/Musify.Api/Endpoints/AlbumEndpoints.cs index 1e56399..2d7a4df 100644 --- a/backend/Hosts/Musify.Api/Endpoints/AlbumEndpoints.cs +++ b/backend/Hosts/Musify.Api/Endpoints/AlbumEndpoints.cs @@ -129,9 +129,9 @@ public static class AlbumEndpoints private static async Task GetAlbums( IMediator mediator, - CancellationToken cancellationToken, string? title, - [AsParameters] PageQuery page) + [AsParameters] PageQuery page, + CancellationToken cancellationToken) { var result = await mediator.Send(new GetAlbumsQuery(title, page.PageNumber, page.PageSize), cancellationToken); return Results.Ok(result); @@ -149,8 +149,8 @@ public static class AlbumEndpoints private static async Task GetAlbumsByUserId( IMediator mediator, long userId, - CancellationToken cancellationToken, - [AsParameters] PageQuery page) + [AsParameters] PageQuery page, + CancellationToken cancellationToken) { var result = await mediator.Send(new GetAlbumsByUserIdQuery(userId, page.PageNumber, page.PageSize), cancellationToken); return result.ToHttpResult(); @@ -177,8 +177,8 @@ public static class AlbumEndpoints private static async Task GetAlbumTracks( IMediator mediator, Guid id, - CancellationToken cancellationToken, - [AsParameters] PageQuery page) + [AsParameters] PageQuery page, + CancellationToken cancellationToken) { var result = await mediator.Send(new GetAlbumTracksQuery(id, page.PageNumber, page.PageSize), cancellationToken); return result.ToHttpResult(); diff --git a/backend/Hosts/Musify.Api/Endpoints/LikeEndpoints.cs b/backend/Hosts/Musify.Api/Endpoints/LikeEndpoints.cs index 6d1ec73..af30182 100644 --- a/backend/Hosts/Musify.Api/Endpoints/LikeEndpoints.cs +++ b/backend/Hosts/Musify.Api/Endpoints/LikeEndpoints.cs @@ -38,8 +38,8 @@ public static class LikeEndpoints private static async Task GetLikedTracks( IMediator mediator, CurrentUser currentUser, - CancellationToken cancellationToken, - [AsParameters] PageQuery page) + [AsParameters] PageQuery page, + CancellationToken cancellationToken) { var result = await mediator.Send(new GetLikedTracksQuery(currentUser.RequiredId, page.PageNumber, page.PageSize), cancellationToken); return Results.Ok(result); diff --git a/backend/Hosts/Musify.Api/Endpoints/PlayListEndpoints.cs b/backend/Hosts/Musify.Api/Endpoints/PlayListEndpoints.cs index 74d51ac..d21d8b4 100644 --- a/backend/Hosts/Musify.Api/Endpoints/PlayListEndpoints.cs +++ b/backend/Hosts/Musify.Api/Endpoints/PlayListEndpoints.cs @@ -125,9 +125,9 @@ public static class PlayListEndpoints IMediator mediator, CurrentUser currentUser, long userId, - CancellationToken cancellationToken, string? name, - [AsParameters] PageQuery page) + [AsParameters] PageQuery page, + CancellationToken cancellationToken) { var result = await mediator.Send(new GetPlayListsByUserIdQuery(userId, name, page.PageNumber, page.PageSize, currentUser.Id), cancellationToken); return Results.Ok(result); @@ -137,8 +137,8 @@ public static class PlayListEndpoints IMediator mediator, CurrentUser currentUser, Guid id, - CancellationToken cancellationToken, - [AsParameters] PageQuery page) + [AsParameters] PageQuery page, + CancellationToken cancellationToken) { var result = await mediator.Send(new GetPlayListTracksQuery(id, page.PageNumber, page.PageSize, currentUser.Id), cancellationToken); return result.ToHttpResult(); diff --git a/backend/Hosts/Musify.Api/Endpoints/TrackEndpoints.cs b/backend/Hosts/Musify.Api/Endpoints/TrackEndpoints.cs index 3db1c4e..c8ee6e1 100644 --- a/backend/Hosts/Musify.Api/Endpoints/TrackEndpoints.cs +++ b/backend/Hosts/Musify.Api/Endpoints/TrackEndpoints.cs @@ -97,10 +97,10 @@ public static class TrackEndpoints private static async Task GetTracks( IMediator mediator, - CancellationToken cancellationToken, string? name, GenreParameter? genre, - [AsParameters] PageQuery page) + [AsParameters] PageQuery page, + CancellationToken cancellationToken) { var result = await mediator.Send(new GetTracksQuery(name, page.PageNumber, page.PageSize, genre?.Value), cancellationToken); return Results.Ok(result); @@ -152,9 +152,9 @@ public static class TrackEndpoints private static async Task GetTracksByUserId( IMediator mediator, long userId, - CancellationToken cancellationToken, string? name, - [AsParameters] PageQuery page) + [AsParameters] PageQuery page, + CancellationToken cancellationToken) { var result = await mediator.Send(new GetTracksByUserIdQuery(userId, name, page.PageNumber, page.PageSize), cancellationToken); return result.ToHttpResult(); diff --git a/backend/Hosts/Musify.Api/Endpoints/UserEndpoints.cs b/backend/Hosts/Musify.Api/Endpoints/UserEndpoints.cs index 5eaac41..1c6815e 100644 --- a/backend/Hosts/Musify.Api/Endpoints/UserEndpoints.cs +++ b/backend/Hosts/Musify.Api/Endpoints/UserEndpoints.cs @@ -100,9 +100,9 @@ public static class UserEndpoints private static async Task GetUsers( IMediator mediator, CurrentUser currentUser, - CancellationToken cancellationToken, string? usernameSearch, - [AsParameters] PageQuery page) + [AsParameters] PageQuery page, + CancellationToken cancellationToken) { var result = await mediator.Send(new GetUsersQuery(page.PageNumber, page.PageSize, usernameSearch, currentUser.Id), cancellationToken); return Results.Ok(result); @@ -162,8 +162,8 @@ public static class UserEndpoints IMediator mediator, CurrentUser currentUser, long id, - CancellationToken cancellationToken, - [AsParameters] PageQuery page) + [AsParameters] PageQuery page, + CancellationToken cancellationToken) { var result = await mediator.Send(new GetUserFollowersQuery(currentUser.Id, id, page.PageNumber, page.PageSize), cancellationToken); return result.ToHttpResult(); @@ -173,8 +173,8 @@ public static class UserEndpoints IMediator mediator, CurrentUser currentUser, long id, - CancellationToken cancellationToken, - [AsParameters] PageQuery page) + [AsParameters] PageQuery page, + CancellationToken cancellationToken) { var result = await mediator.Send(new GetUserFollowingQuery(currentUser.Id, id, page.PageNumber, page.PageSize), cancellationToken); return result.ToHttpResult(); diff --git a/backend/Tests/Musify.Api.Tests/Endpoints/AlbumEndpointsTests.cs b/backend/Tests/Musify.Api.Tests/Endpoints/AlbumEndpointsTests.cs index 4a7f5c9..0a79070 100644 --- a/backend/Tests/Musify.Api.Tests/Endpoints/AlbumEndpointsTests.cs +++ b/backend/Tests/Musify.Api.Tests/Endpoints/AlbumEndpointsTests.cs @@ -4,8 +4,8 @@ using Musify.Api.DataTransferObjects; using Musify.Api.DataTransferObjects.Albums; using Musify.Api.Tests.TestSupport; using Musify.Application.Albums.Responses; -using Musify.Application.Pictures.Responses; using Musify.Application.Contracts; +using Musify.Application.Pictures.Responses; using NSubstitute; using Xunit; diff --git a/backend/Tests/Musify.Api.Tests/TestSupport/ApiTestFixture.cs b/backend/Tests/Musify.Api.Tests/TestSupport/ApiTestFixture.cs index e2a5961..83751b4 100644 --- a/backend/Tests/Musify.Api.Tests/TestSupport/ApiTestFixture.cs +++ b/backend/Tests/Musify.Api.Tests/TestSupport/ApiTestFixture.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; @@ -64,7 +65,7 @@ public sealed class ApiTestFixture : WebApplicationFactory, IAsyncLifet public HttpClient CreateAuthenticatedClient(long userId, string userName = "test-user") { var client = CreateClient(); - client.DefaultRequestHeaders.Add(FakeAuthenticationHandler.UserIdHeader, userId.ToString()); + client.DefaultRequestHeaders.Add(FakeAuthenticationHandler.UserIdHeader, userId.ToString(CultureInfo.InvariantCulture)); client.DefaultRequestHeaders.Add("X-Test-User-Name", userName); return client; } diff --git a/backend/Tests/Musify.Application.Tests/Genres/GetAvailableGenresQueryHandlerTests.cs b/backend/Tests/Musify.Application.Tests/Genres/GetAvailableGenresQueryHandlerTests.cs index 29fd50d..9e63b58 100644 --- a/backend/Tests/Musify.Application.Tests/Genres/GetAvailableGenresQueryHandlerTests.cs +++ b/backend/Tests/Musify.Application.Tests/Genres/GetAvailableGenresQueryHandlerTests.cs @@ -1,7 +1,7 @@ using Musify.Application.Genres; +using Musify.Application.Genres.Responses; using Musify.Domain.ValueObjects; using Xunit; -using Musify.Application.Genres.Responses; namespace Musify.Application.Tests.Genres; diff --git a/backend/Tests/Musify.Application.Tests/TestSupport/HandlerTestBase.cs b/backend/Tests/Musify.Application.Tests/TestSupport/HandlerTestBase.cs index 42b925f..544efda 100644 --- a/backend/Tests/Musify.Application.Tests/TestSupport/HandlerTestBase.cs +++ b/backend/Tests/Musify.Application.Tests/TestSupport/HandlerTestBase.cs @@ -12,7 +12,11 @@ public abstract class HandlerTestBase : IAsyncLifetime public async ValueTask InitializeAsync() => database = await TestDatabase.CreateAsync(); - public ValueTask DisposeAsync() => database.DisposeAsync(); + public ValueTask DisposeAsync() + { + GC.SuppressFinalize(this); + return database.DisposeAsync(); + } protected static ILogger NoOpLogger() => NullLogger.Instance; diff --git a/backend/Tests/Musify.Infrastructure.Tests/MassTransit/DeleteRoutingSlipBuilderTests.cs b/backend/Tests/Musify.Infrastructure.Tests/MassTransit/DeleteRoutingSlipBuilderTests.cs index a2b7af5..d1a0705 100644 --- a/backend/Tests/Musify.Infrastructure.Tests/MassTransit/DeleteRoutingSlipBuilderTests.cs +++ b/backend/Tests/Musify.Infrastructure.Tests/MassTransit/DeleteRoutingSlipBuilderTests.cs @@ -18,7 +18,10 @@ public sealed class DeleteRoutingSlipBuilderTests(InfrastructureTestFixture fixt { var album = await SeedAlbumAsync(new EntityPictures { - OriginalName = "original.webp", SmallName = "small.webp", MediumName = "medium.webp", LargeName = "large.webp" + OriginalName = "original.webp", + SmallName = "small.webp", + MediumName = "medium.webp", + LargeName = "large.webp" }); await using var database = fixture.CreateDatabase(); @@ -116,7 +119,10 @@ public sealed class DeleteRoutingSlipBuilderTests(InfrastructureTestFixture fixt var track = await SeedTrackAsync( new TrackPictures { - OriginalName = "original.webp", SmallName = "s.webp", MediumName = "m.webp", LargeName = "l.webp", + OriginalName = "original.webp", + SmallName = "s.webp", + MediumName = "m.webp", + LargeName = "l.webp", ProcessingStatus = ProcessingStatus.Completed }, new TrackAudio { OriginalName = "audio.mp3", FolderName = "folder", TranscodeStatus = ProcessingStatus.Completed }); diff --git a/backend/Tests/Musify.Infrastructure.Tests/TestSupport/InfrastructureTestFixture.cs b/backend/Tests/Musify.Infrastructure.Tests/TestSupport/InfrastructureTestFixture.cs index a504aa2..7ccc578 100644 --- a/backend/Tests/Musify.Infrastructure.Tests/TestSupport/InfrastructureTestFixture.cs +++ b/backend/Tests/Musify.Infrastructure.Tests/TestSupport/InfrastructureTestFixture.cs @@ -18,6 +18,8 @@ public sealed class InfrastructureTestFixture : IAsyncLifetime public const string S3SecretKey = "musify-test-secret"; public const string S3Bucket = "musify-test-bucket"; + private static readonly string[] S3Actions = ["Admin", "Read", "Write"]; + private readonly PostgreSqlContainer postgres = new PostgreSqlBuilder("postgres:17-alpine") .WithDatabase("musify_test") .WithUsername("postgres") @@ -83,7 +85,7 @@ public sealed class InfrastructureTestFixture : IAsyncLifetime { name = "musify-test", credentials = new[] { new { accessKey = S3AccessKey, secretKey = S3SecretKey } }, - actions = new[] { "Admin", "Read", "Write" } + actions = S3Actions } } }); diff --git a/backend/Tests/Musify.StreamingGateway.Tests/Authentication/TicketValidatorTests.cs b/backend/Tests/Musify.StreamingGateway.Tests/Authentication/TicketValidatorTests.cs index 801e9b6..d96b479 100644 --- a/backend/Tests/Musify.StreamingGateway.Tests/Authentication/TicketValidatorTests.cs +++ b/backend/Tests/Musify.StreamingGateway.Tests/Authentication/TicketValidatorTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Security.Cryptography; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; @@ -36,7 +37,7 @@ public sealed class TicketValidatorTests : IDisposable { var claims = new Dictionary { ["prefix"] = prefix }; if (maxBytes != null) - claims["maxBytes"] = maxBytes.Value.ToString(); + claims["maxBytes"] = maxBytes.Value.ToString(CultureInfo.InvariantCulture); var descriptor = new SecurityTokenDescriptor { diff --git a/backend/Tests/Musify.StreamingGateway.Tests/Middleware/TicketValidationMiddlewareTests.cs b/backend/Tests/Musify.StreamingGateway.Tests/Middleware/TicketValidationMiddlewareTests.cs index a4735f0..96fe153 100644 --- a/backend/Tests/Musify.StreamingGateway.Tests/Middleware/TicketValidationMiddlewareTests.cs +++ b/backend/Tests/Musify.StreamingGateway.Tests/Middleware/TicketValidationMiddlewareTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Security.Cryptography; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging.Abstractions; @@ -39,7 +40,7 @@ public sealed class TicketValidationMiddlewareTests : IDisposable { var claims = new Dictionary { ["prefix"] = prefix }; if (maxBytes != null) - claims["maxBytes"] = maxBytes.Value.ToString(); + claims["maxBytes"] = maxBytes.Value.ToString(CultureInfo.InvariantCulture); return new JsonWebTokenHandler().CreateToken(new SecurityTokenDescriptor {