Compare commits
10
Commits
0cba32f776
...
b8524aa5a8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8524aa5a8 | ||
|
|
73e37dbecd | ||
|
|
16478678b5 | ||
|
|
7f51145915 | ||
|
|
28a7c4a98c | ||
|
|
2d0867e68a | ||
|
|
bc6ca3651f | ||
|
|
c4cf5af7f1 | ||
|
|
fcf3093ad8 | ||
|
|
405df082cc |
+34
-20
@@ -18,7 +18,7 @@ generated_code = true
|
|||||||
[*.cs]
|
[*.cs]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
csharp_style_namespace_declarations = file_scoped:warning
|
csharp_style_namespace_declarations = block_scoped:warning
|
||||||
csharp_using_directive_placement = outside_namespace:warning
|
csharp_using_directive_placement = outside_namespace:warning
|
||||||
dotnet_sort_system_directives_first = true
|
dotnet_sort_system_directives_first = true
|
||||||
dotnet_separate_import_directive_groups = false
|
dotnet_separate_import_directive_groups = false
|
||||||
@@ -33,33 +33,47 @@ csharp_style_prefer_primary_constructors = true:suggestion
|
|||||||
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
|
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
|
||||||
dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
|
dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
|
||||||
|
|
||||||
|
# Naming. Roslyn applies the first matching rule in file order, so the specific rules come first.
|
||||||
|
# Private fields are _camelCase whatever their modifiers (static, readonly, const).
|
||||||
dotnet_naming_rule.private_fields.symbols = private_fields
|
dotnet_naming_rule.private_fields.symbols = private_fields
|
||||||
dotnet_naming_rule.private_fields.style = camel_case
|
dotnet_naming_rule.private_fields.style = underscore_camel_case
|
||||||
dotnet_naming_rule.private_fields.severity = warning
|
dotnet_naming_rule.private_fields.severity = warning
|
||||||
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
||||||
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
|
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
|
||||||
dotnet_naming_symbols.private_fields.required_modifiers =
|
dotnet_naming_style.underscore_camel_case.capitalization = camel_case
|
||||||
dotnet_naming_style.camel_case.capitalization = camel_case
|
dotnet_naming_style.underscore_camel_case.required_prefix = _
|
||||||
|
|
||||||
dotnet_naming_rule.private_constants.symbols = private_constants
|
# Non-private fields (constants, static readonly, etc.) are PascalCase.
|
||||||
dotnet_naming_rule.private_constants.style = pascal_case
|
dotnet_naming_rule.other_fields.symbols = other_fields
|
||||||
dotnet_naming_rule.private_constants.severity = warning
|
dotnet_naming_rule.other_fields.style = pascal_case
|
||||||
dotnet_naming_symbols.private_constants.applicable_kinds = field
|
dotnet_naming_rule.other_fields.severity = warning
|
||||||
dotnet_naming_symbols.private_constants.required_modifiers = const
|
dotnet_naming_symbols.other_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.other_fields.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
|
||||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||||
|
|
||||||
# Analyzer rules that would need behaviour or API changes; downgraded in P0, revisit in P15.
|
dotnet_naming_rule.interfaces.symbols = interfaces
|
||||||
|
dotnet_naming_rule.interfaces.style = interface_style
|
||||||
|
dotnet_naming_rule.interfaces.severity = warning
|
||||||
|
dotnet_naming_symbols.interfaces.applicable_kinds = interface
|
||||||
|
dotnet_naming_style.interface_style.capitalization = pascal_case
|
||||||
|
dotnet_naming_style.interface_style.required_prefix = I
|
||||||
|
|
||||||
|
dotnet_naming_rule.types_and_members.symbols = types_and_members
|
||||||
|
dotnet_naming_rule.types_and_members.style = pascal_case
|
||||||
|
dotnet_naming_rule.types_and_members.severity = warning
|
||||||
|
dotnet_naming_symbols.types_and_members.applicable_kinds = class, struct, enum, delegate, property, method, event
|
||||||
|
dotnet_naming_symbols.types_and_members.applicable_accessibilities = *
|
||||||
|
|
||||||
|
# CA1848/CA1873 would turn every log call into a [LoggerMessage] source-generated method; CA1716 flags the
|
||||||
|
# EF-style IDatabase.Set<T>() and the Musify.Application.Shared namespace, whose names are deliberate.
|
||||||
dotnet_diagnostic.CA1848.severity = suggestion
|
dotnet_diagnostic.CA1848.severity = suggestion
|
||||||
dotnet_diagnostic.CA1873.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.CA1716.severity = suggestion
|
||||||
dotnet_diagnostic.CA1000.severity = suggestion
|
# The routing slip builders are stateless services resolved from DI; they stay instance members.
|
||||||
dotnet_diagnostic.CA1725.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1816.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1822.severity = suggestion
|
dotnet_diagnostic.CA1822.severity = suggestion
|
||||||
dotnet_diagnostic.CA1852.severity = suggestion
|
|
||||||
dotnet_diagnostic.CA1068.severity = suggestion
|
# Test names follow Method_Scenario_Expected.
|
||||||
dotnet_diagnostic.CA1711.severity = suggestion
|
[Tests/**.cs]
|
||||||
dotnet_diagnostic.CA1861.severity = suggestion
|
dotnet_diagnostic.CA1707.severity = none
|
||||||
|
# xUnit collection fixtures are conventionally named ...Collection.
|
||||||
|
dotnet_diagnostic.CA1711.severity = none
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Musify: backend
|
||||||
|
|
||||||
|
.NET 10 solution (`Musify.slnx`): `Core/` (Domain, Application, Infrastructure), `Hosts/` (Api, Worker, StreamingGateway) and `Tests/`. Architecture and flows are in `../docs/`.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
Run from `backend/`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dotnet build Musify.slnx # warnings are errors, including unused usings (IDE0005)
|
||||||
|
dotnet test Musify.slnx # Infrastructure and Api tests need Docker (Testcontainers)
|
||||||
|
dotnet format Musify.slnx --verify-no-changes
|
||||||
|
```
|
||||||
|
|
||||||
|
- Read the totals of `dotnet test` (`total:`, `succeeded:`) before trusting it; don't chain it with `&&` into a commit.
|
||||||
|
- EF migrations: `dotnet ef migrations add <Name> --project Core/Musify.Infrastructure --startup-project Core/Musify.Infrastructure` (set `Database__ConnectionString` to any value). A model change needs a migration; the entity model lives in `ModelConfiguration.Apply`, which the in-memory test database reuses.
|
||||||
|
- An HTTP contract change (routes, DTOs, responses) needs the web-player schema regenerated (`web-player/src/lib/api/schema.d.ts`, from `/openapi/v1.json`) and `npm run check` clean.
|
||||||
|
- A MassTransit message change (events, routing slip arguments, process kinds) means the queues must be drained before deploying; note it in the commit.
|
||||||
|
|
||||||
|
## Style
|
||||||
|
|
||||||
|
- Block-scoped namespaces (`namespace X { }`; file-scoped `namespace X;` is not allowed), usings outside the namespace and sorted (`.editorconfig` enforces it).
|
||||||
|
- Private fields are `_camelCase` whatever their modifiers (including `static readonly` and `const`); non-private constants and static fields, types, methods and properties are PascalCase; interfaces start with `I`; explicit accessibility. Enforced by `.editorconfig` (IDE1006).
|
||||||
|
- `ArgumentNullException.ThrowIfNull(x)` without `nameof`.
|
||||||
|
- Use `PlayList` (not `Playlist`) in code; configuration POCOs end in `Configuration`; injected services are named after their type (`storageService`).
|
||||||
|
- User-facing strings are in English (the web-player owns translation).
|
||||||
|
- Trim stored names; normalized columns go through `TextNormalizer.Normalize`.
|
||||||
|
- Format ids and numbers with `CultureInfo.InvariantCulture`; put `CancellationToken` last.
|
||||||
|
|
||||||
|
## Application layer
|
||||||
|
|
||||||
|
- One file per use case, `<Feature>/<UseCase>.cs`, holding the record and its handler; responses go in `Responses/`.
|
||||||
|
- Errors come from `AppErrors` (`NotFound`, `Forbidden` = 403 for "not the owner", `Conflict`). `Error.Unauthorized` (401) only means "not signed in". The success value is `Result.Success`; the API maps it to 204.
|
||||||
|
- Handlers that cannot fail return plain types. Lists are paginated through `PageRequest` / `ToPaginatedAsync`, ordered with an `Id` tiebreak, and public reads return only `LifeCycleStatus.Active` rows.
|
||||||
|
- Owned-entity operations go through `FindOwnedAsync` (non-active entities are not found).
|
||||||
|
- No try/catch around `SaveChangesAsync` or `PublishAsync` in handlers: the outbox makes publish + save atomic. Infrastructure either handles an error or lets it propagate; never log and rethrow.
|
||||||
|
- List query records keep `int PageNumber, int PageSize`; the API binds them through `PageQuery` and validates them with `PageQueryValidator`.
|
||||||
|
|
||||||
|
## API layer
|
||||||
|
|
||||||
|
- Routes: `{id:guid}` for the resource, `{trackId:guid}` for sub-resources, `{id:long}` / `{userId:long}` for user ids.
|
||||||
|
- Every endpoint returns `IResult` (`Results.Ok` when the handler cannot fail, `ToHttpResult()` otherwise).
|
||||||
|
- Validation limits live in `Validators/Limits.cs`; the database column length wins when a limit disagrees with the web-player. Upload allow-lists are in `Validators/Uploads.cs`.
|
||||||
|
- `.WithSummary(...)` is sentence case without a trailing period.
|
||||||
|
|
||||||
|
## Infrastructure and hosts
|
||||||
|
|
||||||
|
- MassTransit routing slips are built from `RoutingSlips.Create` / `AddStep` / `TrackFaults`; activities for the three owner types share generic bases (`MarkLifeCycleActivity<T>`, `DeleteEntityActivity<T>`, `UpdatePicturesActivity<T>`).
|
||||||
|
- Recurring work is Hangfire (`RecurringJobsRegistrar`); intervals are in seconds and rounded up to whole minutes.
|
||||||
|
- The Worker calls `AddApplication` and validates its service container on build, so any handler dependency must be registered there. Shared config sections in its `appsettings.json` mirror the API's.
|
||||||
|
- The StreamingGateway references neither Application nor Infrastructure and keeps its own options.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
- xUnit v3; pass `TestContext.Current.CancellationToken` to methods that take a token (analyzer xUnit1051).
|
||||||
|
- Test names are `Method_Scenario_Expected`.
|
||||||
|
- Application tests use the in-memory SQLite `TestDatabase`; Infrastructure and Api tests use Testcontainers (Postgres, SeaweedFS). MassTransit sagas and consumers are tested on the MassTransit test harness.
|
||||||
|
- `Infrastructure` exposes its internals to `Musify.Infrastructure.Tests`.
|
||||||
@@ -7,8 +7,8 @@ using Musify.Application.Shared;
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Albums;
|
namespace Musify.Application.Albums
|
||||||
|
{
|
||||||
/// <summary>Appends your own tracks to an album in the given order, skipping the ones it already has.</summary>
|
/// <summary>Appends your own tracks to an album in the given order, skipping the ones it already has.</summary>
|
||||||
public record AddTracksToAlbumCommand(long UserId, Guid AlbumId, IReadOnlyList<Guid> TrackIds)
|
public record AddTracksToAlbumCommand(long UserId, Guid AlbumId, IReadOnlyList<Guid> TrackIds)
|
||||||
: ICommand<ErrorOr<Success>>;
|
: ICommand<ErrorOr<Success>>;
|
||||||
@@ -75,3 +75,4 @@ public class AddTracksToAlbumCommandHandler(
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ using Musify.Application.Shared;
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Albums;
|
namespace Musify.Application.Albums
|
||||||
|
{
|
||||||
public record CreateAlbumCommand(
|
public record CreateAlbumCommand(
|
||||||
long UserId,
|
long UserId,
|
||||||
string Title,
|
string Title,
|
||||||
@@ -70,3 +70,4 @@ public class CreateAlbumCommandHandler(
|
|||||||
return AlbumApplicationResponse.FromEntity(album, trackCount: 0);
|
return AlbumApplicationResponse.FromEntity(album, trackCount: 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ using Musify.Application.Events;
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Albums;
|
namespace Musify.Application.Albums
|
||||||
|
{
|
||||||
public record DeleteAlbumCommand(long UserId, Guid AlbumId)
|
public record DeleteAlbumCommand(long UserId, Guid AlbumId)
|
||||||
: ICommand<ErrorOr<Success>>;
|
: ICommand<ErrorOr<Success>>;
|
||||||
|
|
||||||
@@ -34,3 +34,4 @@ public class DeleteAlbumCommandHandler(
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using Musify.Application.Albums.Responses;
|
|||||||
using Musify.Application.Contracts;
|
using Musify.Application.Contracts;
|
||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Albums;
|
namespace Musify.Application.Albums
|
||||||
|
{
|
||||||
public record GetAlbumByIdQuery(Guid Id)
|
public record GetAlbumByIdQuery(Guid Id)
|
||||||
: IQuery<ErrorOr<AlbumApplicationResponse>>;
|
: IQuery<ErrorOr<AlbumApplicationResponse>>;
|
||||||
|
|
||||||
@@ -27,3 +27,4 @@ public class GetAlbumByIdQueryHandler(IDatabase database)
|
|||||||
: album;
|
: album;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ using Musify.Application.Contracts;
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
using Musify.Application.Tracks.Responses;
|
using Musify.Application.Tracks.Responses;
|
||||||
|
|
||||||
namespace Musify.Application.Albums;
|
namespace Musify.Application.Albums
|
||||||
|
{
|
||||||
public record GetAlbumTracksQuery(
|
public record GetAlbumTracksQuery(
|
||||||
Guid AlbumId,
|
Guid AlbumId,
|
||||||
int PageNumber,
|
int PageNumber,
|
||||||
@@ -36,3 +36,4 @@ public class GetAlbumTracksQueryHandler(IDatabase database)
|
|||||||
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ using Musify.Application.Albums.Responses;
|
|||||||
using Musify.Application.Contracts;
|
using Musify.Application.Contracts;
|
||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Albums;
|
namespace Musify.Application.Albums
|
||||||
|
{
|
||||||
public record GetAlbumsQuery(string? Title, int PageNumber, int PageSize)
|
public record GetAlbumsQuery(string? Title, int PageNumber, int PageSize)
|
||||||
: IQuery<PaginatedResponse<AlbumApplicationResponse>>;
|
: IQuery<PaginatedResponse<AlbumApplicationResponse>>;
|
||||||
|
|
||||||
@@ -31,3 +31,4 @@ public class GetAlbumsQueryHandler(IDatabase database)
|
|||||||
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using Musify.Application.Albums.Responses;
|
|||||||
using Musify.Application.Contracts;
|
using Musify.Application.Contracts;
|
||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Albums;
|
namespace Musify.Application.Albums
|
||||||
|
{
|
||||||
public record GetAlbumsByUserIdQuery(long UserId, int PageNumber, int PageSize)
|
public record GetAlbumsByUserIdQuery(long UserId, int PageNumber, int PageSize)
|
||||||
: IQuery<ErrorOr<PaginatedResponse<AlbumApplicationResponse>>>;
|
: IQuery<ErrorOr<PaginatedResponse<AlbumApplicationResponse>>>;
|
||||||
|
|
||||||
@@ -31,3 +31,4 @@ public class GetAlbumsByUserIdQueryHandler(IDatabase database)
|
|||||||
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Musify.Application.Albums.Responses;
|
using Musify.Application.Albums.Responses;
|
||||||
using Musify.Application.Contracts;
|
using Musify.Application.Contracts;
|
||||||
|
|
||||||
namespace Musify.Application.Albums;
|
namespace Musify.Application.Albums
|
||||||
|
{
|
||||||
public record GetRecentlyListenedAlbumsQuery(long UserId, int Limit)
|
public record GetRecentlyListenedAlbumsQuery(long UserId, int Limit)
|
||||||
: IQuery<IReadOnlyList<AlbumApplicationResponse>>;
|
: IQuery<IReadOnlyList<AlbumApplicationResponse>>;
|
||||||
|
|
||||||
@@ -49,3 +49,4 @@ public class GetRecentlyListenedAlbumsQueryHandler(IDatabase database)
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using Microsoft.Extensions.Logging;
|
|||||||
using Musify.Application.Contracts;
|
using Musify.Application.Contracts;
|
||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Albums;
|
namespace Musify.Application.Albums
|
||||||
|
{
|
||||||
public record RemoveTrackFromAlbumCommand(long UserId, Guid AlbumId, Guid TrackId)
|
public record RemoveTrackFromAlbumCommand(long UserId, Guid AlbumId, Guid TrackId)
|
||||||
: ICommand<ErrorOr<Success>>;
|
: ICommand<ErrorOr<Success>>;
|
||||||
|
|
||||||
@@ -43,3 +43,4 @@ public class RemoveTrackFromAlbumCommandHandler(
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using Musify.Application.Pictures.Responses;
|
|||||||
using Musify.Application.Services;
|
using Musify.Application.Services;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Albums;
|
namespace Musify.Application.Albums
|
||||||
|
{
|
||||||
public record RequestAlbumPictureUploadCommand(
|
public record RequestAlbumPictureUploadCommand(
|
||||||
long UserId,
|
long UserId,
|
||||||
string FileType,
|
string FileType,
|
||||||
@@ -22,3 +22,4 @@ public class RequestAlbumPictureUploadCommandHandler(UploadIntentIssuer issuer,
|
|||||||
request.UserId, UploadIntentPurpose.AlbumPicture, albumConfiguration,
|
request.UserId, UploadIntentPurpose.AlbumPicture, albumConfiguration,
|
||||||
request.FileType, request.ContentType, request.ExpectedSizeBytes, cancellationToken);
|
request.FileType, request.ContentType, request.ExpectedSizeBytes, cancellationToken);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
|
|
||||||
namespace Musify.Application.Albums.Responses;
|
namespace Musify.Application.Albums.Responses
|
||||||
|
{
|
||||||
public record AlbumApplicationResponse(
|
public record AlbumApplicationResponse(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
string Title,
|
string Title,
|
||||||
@@ -38,3 +38,4 @@ public record AlbumApplicationResponse(
|
|||||||
coverTrackIds ?? []);
|
coverTrackIds ?? []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Albums.Responses;
|
namespace Musify.Application.Albums.Responses
|
||||||
|
{
|
||||||
public static class AlbumProjections
|
public static class AlbumProjections
|
||||||
{
|
{
|
||||||
public const int CoverTrackCount = 4;
|
public const int CoverTrackCount = 4;
|
||||||
@@ -29,3 +29,4 @@ public static class AlbumProjections
|
|||||||
.Select(albumTrack => albumTrack.TrackId)
|
.Select(albumTrack => albumTrack.TrackId)
|
||||||
.ToList()));
|
.ToList()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ using Musify.Application.Services;
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Albums;
|
namespace Musify.Application.Albums
|
||||||
|
{
|
||||||
public record UpdateAlbumCommand(
|
public record UpdateAlbumCommand(
|
||||||
long UserId,
|
long UserId,
|
||||||
Guid AlbumId,
|
Guid AlbumId,
|
||||||
@@ -71,3 +71,4 @@ public class UpdateAlbumCommandHandler(
|
|||||||
.SingleAsync(cancellationToken);
|
.SingleAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using Musify.Application.Configuration;
|
using Musify.Application.Configuration;
|
||||||
using Musify.Application.Services;
|
using Musify.Application.Services;
|
||||||
|
|
||||||
namespace Musify.Application;
|
namespace Musify.Application
|
||||||
|
{
|
||||||
public static class ApplicationDependencyInjection
|
public static class ApplicationDependencyInjection
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddApplication(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddApplication(this IServiceCollection services, IConfiguration configuration)
|
||||||
@@ -26,3 +26,4 @@ public static class ApplicationDependencyInjection
|
|||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
public class AlbumConfiguration : IConfigurationOptions, IPictureOwnerConfiguration
|
public class AlbumConfiguration : IConfigurationOptions, IPictureOwnerConfiguration
|
||||||
{
|
{
|
||||||
public static string SectionName => "Album";
|
public static string SectionName => "Album";
|
||||||
@@ -12,3 +12,4 @@ public class AlbumConfiguration : IConfigurationOptions, IPictureOwnerConfigurat
|
|||||||
[Required]
|
[Required]
|
||||||
public PictureSizes PicturesSizes { get; set; } = new();
|
public PictureSizes PicturesSizes { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
public class ApplicationStorageConfiguration : IConfigurationOptions
|
public class ApplicationStorageConfiguration : IConfigurationOptions
|
||||||
{
|
{
|
||||||
public static string SectionName => "ApplicationStorage";
|
public static string SectionName => "ApplicationStorage";
|
||||||
@@ -10,3 +10,4 @@ public class ApplicationStorageConfiguration : IConfigurationOptions
|
|||||||
[StringLength(128, MinimumLength = 1)]
|
[StringLength(128, MinimumLength = 1)]
|
||||||
public required string Bucket { get; set; }
|
public required string Bucket { get; set; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ using Microsoft.Extensions.Configuration;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
public static class ConfigurationDependencyInjection
|
public static class ConfigurationDependencyInjection
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddValidatedOptions<T>(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddValidatedOptions<T>(this IServiceCollection services, IConfiguration configuration)
|
||||||
@@ -27,3 +27,4 @@ public static class ConfigurationDependencyInjection
|
|||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Mediator;
|
using Mediator;
|
||||||
using Musify.Application.Configuration.Responses;
|
using Musify.Application.Configuration.Responses;
|
||||||
|
|
||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
|
|
||||||
public record GetPlaybackPublicConfigQuery : IQuery<PlaybackPublicConfigResponse>;
|
public record GetPlaybackPublicConfigQuery : IQuery<PlaybackPublicConfigResponse>;
|
||||||
|
|
||||||
@@ -14,3 +14,4 @@ public class GetPlaybackPublicConfigQueryHandler(PlaybackConfiguration playbackC
|
|||||||
playbackConfiguration.AllowAnonymousListening,
|
playbackConfiguration.AllowAnonymousListening,
|
||||||
playbackConfiguration.AnonymousFragmentSeconds));
|
playbackConfiguration.AnonymousFragmentSeconds));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
public interface IConfigurationOptions
|
public interface IConfigurationOptions
|
||||||
{
|
{
|
||||||
public static abstract string SectionName { get; }
|
public static abstract string SectionName { get; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
public class MixConfiguration : IConfigurationOptions
|
public class MixConfiguration : IConfigurationOptions
|
||||||
{
|
{
|
||||||
public static string SectionName => "Mix";
|
public static string SectionName => "Mix";
|
||||||
@@ -9,3 +9,4 @@ public class MixConfiguration : IConfigurationOptions
|
|||||||
[Range(4, 100)]
|
[Range(4, 100)]
|
||||||
public int ItemsPerMix { get; set; } = 12;
|
public int ItemsPerMix { get; set; } = 12;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Globalization;
|
||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
public enum PictureSize
|
public enum PictureSize
|
||||||
{
|
{
|
||||||
Small,
|
Small,
|
||||||
@@ -41,10 +42,10 @@ public class PictureRoutes
|
|||||||
public string BuildPicturePath(PictureSize size, string pictureName) => StorageKey.Combine(FolderPath(size), pictureName);
|
public string BuildPicturePath(PictureSize size, string pictureName) => StorageKey.Combine(FolderPath(size), pictureName);
|
||||||
|
|
||||||
public string BuildOriginalPicturePath(long userId, string 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) =>
|
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
|
public class PictureSizes
|
||||||
@@ -88,3 +89,4 @@ public static class PictureSizeParser
|
|||||||
? size
|
? size
|
||||||
: PictureSize.Medium;
|
: PictureSize.Medium;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
public class PlayListConfiguration : IConfigurationOptions, IPictureOwnerConfiguration
|
public class PlayListConfiguration : IConfigurationOptions, IPictureOwnerConfiguration
|
||||||
{
|
{
|
||||||
public static string SectionName => "PlayList";
|
public static string SectionName => "PlayList";
|
||||||
@@ -12,3 +12,4 @@ public class PlayListConfiguration : IConfigurationOptions, IPictureOwnerConfigu
|
|||||||
[Required]
|
[Required]
|
||||||
public PictureSizes PicturesSizes { get; set; } = new();
|
public PictureSizes PicturesSizes { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
|
|
||||||
public sealed class PlaybackConfiguration : IConfigurationOptions
|
public sealed class PlaybackConfiguration : IConfigurationOptions
|
||||||
{
|
{
|
||||||
@@ -15,3 +15,4 @@ public sealed class PlaybackConfiguration : IConfigurationOptions
|
|||||||
[Range(1, int.MaxValue)]
|
[Range(1, int.MaxValue)]
|
||||||
public int EstimatedAudioBytesPerSecond { get; set; } = 16_000;
|
public int EstimatedAudioBytesPerSecond { get; set; } = 16_000;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+3
-2
@@ -1,4 +1,5 @@
|
|||||||
namespace Musify.Application.Configuration.Responses;
|
namespace Musify.Application.Configuration.Responses
|
||||||
|
{
|
||||||
|
|
||||||
public record PlaybackPublicConfigResponse(bool AllowAnonymousListening, int AnonymousFragmentSeconds);
|
public record PlaybackPublicConfigResponse(bool AllowAnonymousListening, int AnonymousFragmentSeconds);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
public class StreamGatewayConfiguration : IConfigurationOptions
|
public class StreamGatewayConfiguration : IConfigurationOptions
|
||||||
{
|
{
|
||||||
public static string SectionName => "StreamGateway";
|
public static string SectionName => "StreamGateway";
|
||||||
@@ -13,3 +13,4 @@ public class StreamGatewayConfiguration : IConfigurationOptions
|
|||||||
[Required]
|
[Required]
|
||||||
public string AudioFileName { get; set; } = "audio.m4a";
|
public string AudioFileName { get; set; } = "audio.m4a";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Globalization;
|
||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
public class TrackConfiguration : IConfigurationOptions, IPictureOwnerConfiguration
|
public class TrackConfiguration : IConfigurationOptions, IPictureOwnerConfiguration
|
||||||
{
|
{
|
||||||
public static string SectionName => "Track";
|
public static string SectionName => "Track";
|
||||||
@@ -48,8 +49,9 @@ public class TrackRoutes : PictureRoutes
|
|||||||
public string PresetLargePicturePath => StorageKey.Combine(ParentFolder, PresetLargePicture);
|
public string PresetLargePicturePath => StorageKey.Combine(ParentFolder, PresetLargePicture);
|
||||||
|
|
||||||
public string BuildOriginalAudioPath(long userId, string audioName) =>
|
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) =>
|
public string BuildProcessedAudioPath(string folderName) =>
|
||||||
StorageKey.Combine(ParentFolder, ProcessedAudiosFolder, folderName);
|
StorageKey.Combine(ParentFolder, ProcessedAudiosFolder, folderName);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace Musify.Application.Configuration;
|
namespace Musify.Application.Configuration
|
||||||
|
{
|
||||||
public class UploadIntentConfiguration : IConfigurationOptions
|
public class UploadIntentConfiguration : IConfigurationOptions
|
||||||
{
|
{
|
||||||
public static string SectionName => "UploadIntent";
|
public static string SectionName => "UploadIntent";
|
||||||
@@ -39,3 +39,4 @@ public class UploadIntentConfiguration : IConfigurationOptions
|
|||||||
[Range(60, 86400)]
|
[Range(60, 86400)]
|
||||||
public int TempCleanupJobIntervalSeconds { get; set; } = 3600;
|
public int TempCleanupJobIntervalSeconds { get; set; } = 3600;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Musify.Application.Contracts;
|
namespace Musify.Application.Contracts
|
||||||
|
{
|
||||||
public record AudioTranscodeResult(int StatusCode, TimeSpan Duration);
|
public record AudioTranscodeResult(int StatusCode, TimeSpan Duration);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace Musify.Application.Contracts;
|
namespace Musify.Application.Contracts
|
||||||
|
{
|
||||||
public interface IAudioTranscoderService
|
public interface IAudioTranscoderService
|
||||||
{
|
{
|
||||||
public Task<AudioTranscodeResult> TranscodeToAudioFileAsync(Stream audioStream, string destinationPath, CancellationToken cancellationToken);
|
public Task<AudioTranscodeResult> TranscodeToAudioFileAsync(Stream audioStream, string destinationPath, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ using ErrorOr;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
|
|
||||||
namespace Musify.Application.Contracts;
|
namespace Musify.Application.Contracts
|
||||||
|
{
|
||||||
public interface IDatabase
|
public interface IDatabase
|
||||||
{
|
{
|
||||||
public DbSet<User> Users { get; }
|
public DbSet<User> Users { get; }
|
||||||
@@ -34,7 +34,10 @@ public interface IDatabase
|
|||||||
|
|
||||||
public Task<IDatabaseTransaction> BeginTransactionAsync(System.Data.IsolationLevel isolationLevel, CancellationToken cancellationToken);
|
public Task<IDatabaseTransaction> BeginTransactionAsync(System.Data.IsolationLevel isolationLevel, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
public DbSet<TEntity> Set<TEntity>() where TEntity : class;
|
||||||
|
|
||||||
public Task<int> SaveChangesAsync(CancellationToken cancellationToken);
|
public Task<int> SaveChangesAsync(CancellationToken cancellationToken);
|
||||||
|
|
||||||
public Task<ErrorOr<Success>> TrySaveChangesAsync(Error onUniqueViolation, CancellationToken cancellationToken);
|
public Task<ErrorOr<Success>> TrySaveChangesAsync(Error onUniqueViolation, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace Musify.Application.Contracts;
|
namespace Musify.Application.Contracts
|
||||||
|
{
|
||||||
public interface IDatabaseTransaction : IAsyncDisposable
|
public interface IDatabaseTransaction : IAsyncDisposable
|
||||||
{
|
{
|
||||||
public Task CommitAsync(CancellationToken cancellationToken = default);
|
public Task CommitAsync(CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace Musify.Application.Contracts;
|
namespace Musify.Application.Contracts
|
||||||
|
{
|
||||||
public interface IEventBus
|
public interface IEventBus
|
||||||
{
|
{
|
||||||
public Task PublishAsync<T>(T message, CancellationToken cancellationToken) where T : class;
|
public Task PublishAsync<T>(T message, CancellationToken cancellationToken) where T : class;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace Musify.Application.Contracts;
|
namespace Musify.Application.Contracts
|
||||||
|
{
|
||||||
public interface IPictureService
|
public interface IPictureService
|
||||||
{
|
{
|
||||||
public Task<Stream> ResizePictureAsWebpAsync(Stream pictureStream, int width, int height, CancellationToken cancellationToken);
|
public Task<Stream> ResizePictureAsWebpAsync(Stream pictureStream, int width, int height, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
namespace Musify.Application.Contracts;
|
namespace Musify.Application.Contracts
|
||||||
|
{
|
||||||
public interface IStorageService
|
public interface IStorageService
|
||||||
{
|
{
|
||||||
/// <summary>Opens an object for reading, or returns null when it does not exist.</summary>
|
/// <summary>Opens an object for reading, or returns null when it does not exist.</summary>
|
||||||
@@ -22,3 +22,4 @@ public interface IStorageService
|
|||||||
|
|
||||||
public IAsyncEnumerable<StorageObject> ListObjectsAsync(string bucket, string prefix, CancellationToken cancellationToken);
|
public IAsyncEnumerable<StorageObject> ListObjectsAsync(string bucket, string prefix, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
namespace Musify.Application.Contracts;
|
namespace Musify.Application.Contracts
|
||||||
|
{
|
||||||
public interface IStreamTicketService
|
public interface IStreamTicketService
|
||||||
{
|
{
|
||||||
public StreamTicket IssueTicket(long? userId, string keyPrefix, long? maxBytes = null);
|
public StreamTicket IssueTicket(long? userId, string keyPrefix, long? maxBytes = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record StreamTicket(string Token, int ExpiresInSeconds);
|
public record StreamTicket(string Token, int ExpiresInSeconds);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Musify.Application.Contracts;
|
namespace Musify.Application.Contracts
|
||||||
|
{
|
||||||
public record ObjectMetaData(string ContentType, long ContentLength);
|
public record ObjectMetaData(string ContentType, long ContentLength);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Musify.Application.Contracts;
|
namespace Musify.Application.Contracts
|
||||||
|
{
|
||||||
public record StorageObject(string Key, DateTime LastModifiedUtc);
|
public record StorageObject(string Key, DateTime LastModifiedUtc);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record AlbumPictureProcessed(Guid AlbumId);
|
public record AlbumPictureProcessed(Guid AlbumId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record AlbumPictureProcessingFailed(Guid AlbumId);
|
public record AlbumPictureProcessingFailed(Guid AlbumId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record AlbumProcessingFailed(
|
public record AlbumProcessingFailed(
|
||||||
Guid AlbumId,
|
Guid AlbumId,
|
||||||
string? Bucket,
|
string? Bucket,
|
||||||
string? PictureKey);
|
string? PictureKey);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record CreateAlbumResourcesEvent(
|
public record CreateAlbumResourcesEvent(
|
||||||
Guid AlbumId,
|
Guid AlbumId,
|
||||||
Guid PictureIntentId,
|
Guid PictureIntentId,
|
||||||
@@ -9,3 +9,4 @@ public record CreateAlbumResourcesEvent(
|
|||||||
string PictureSourceKey,
|
string PictureSourceKey,
|
||||||
string PictureDestinationKey,
|
string PictureDestinationKey,
|
||||||
ImageSizes Sizes);
|
ImageSizes Sizes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record CreatePlayListResourcesEvent(
|
public record CreatePlayListResourcesEvent(
|
||||||
Guid PlayListId,
|
Guid PlayListId,
|
||||||
Guid PictureIntentId,
|
Guid PictureIntentId,
|
||||||
@@ -9,3 +9,4 @@ public record CreatePlayListResourcesEvent(
|
|||||||
string PictureSourceKey,
|
string PictureSourceKey,
|
||||||
string PictureDestinationKey,
|
string PictureDestinationKey,
|
||||||
ImageSizes Sizes);
|
ImageSizes Sizes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record CreateTrackResourcesEvent(
|
public record CreateTrackResourcesEvent(
|
||||||
Guid TrackId,
|
Guid TrackId,
|
||||||
Guid PictureIntentId,
|
Guid PictureIntentId,
|
||||||
@@ -13,3 +13,4 @@ public record CreateTrackResourcesEvent(
|
|||||||
string AudioDestinationKey,
|
string AudioDestinationKey,
|
||||||
string AudioProcessedFolderKey,
|
string AudioProcessedFolderKey,
|
||||||
ImageSizes Sizes);
|
ImageSizes Sizes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record DeleteAlbumEvent(
|
public record DeleteAlbumEvent(
|
||||||
Guid AlbumId,
|
Guid AlbumId,
|
||||||
long UserId);
|
long UserId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record DeletePlayListEvent(
|
public record DeletePlayListEvent(
|
||||||
Guid PlayListId,
|
Guid PlayListId,
|
||||||
long UserId);
|
long UserId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record DeleteTrackEvent(
|
public record DeleteTrackEvent(
|
||||||
Guid TrackId,
|
Guid TrackId,
|
||||||
long UserId);
|
long UserId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record PlayListPictureProcessed(Guid PlayListId);
|
public record PlayListPictureProcessed(Guid PlayListId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record PlayListPictureProcessingFailed(Guid PlayListId);
|
public record PlayListPictureProcessingFailed(Guid PlayListId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record PlayListProcessingFailed(
|
public record PlayListProcessingFailed(
|
||||||
Guid PlayListId,
|
Guid PlayListId,
|
||||||
string? Bucket,
|
string? Bucket,
|
||||||
string? PictureKey);
|
string? PictureKey);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record TrackAudioProcessed(Guid TrackId);
|
public record TrackAudioProcessed(Guid TrackId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record TrackAudioProcessingFailed(Guid TrackId);
|
public record TrackAudioProcessingFailed(Guid TrackId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record TrackPictureProcessed(Guid TrackId);
|
public record TrackPictureProcessed(Guid TrackId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record TrackPictureProcessingFailed(Guid TrackId);
|
public record TrackPictureProcessingFailed(Guid TrackId);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record TrackProcessingFailed(
|
public record TrackProcessingFailed(
|
||||||
Guid TrackId,
|
Guid TrackId,
|
||||||
string? Bucket,
|
string? Bucket,
|
||||||
string? PictureKey,
|
string? PictureKey,
|
||||||
string? AudioKey);
|
string? AudioKey);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record UpdateAlbumPictureEvent(
|
public record UpdateAlbumPictureEvent(
|
||||||
Guid AlbumId,
|
Guid AlbumId,
|
||||||
string Bucket,
|
string Bucket,
|
||||||
string SourceKey,
|
string SourceKey,
|
||||||
ImageSizes Sizes);
|
ImageSizes Sizes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record UpdateAlbumPictureSourceEvent(
|
public record UpdateAlbumPictureSourceEvent(
|
||||||
Guid AlbumId,
|
Guid AlbumId,
|
||||||
Guid PictureIntentId,
|
Guid PictureIntentId,
|
||||||
@@ -9,3 +9,4 @@ public record UpdateAlbumPictureSourceEvent(
|
|||||||
string PictureSourceKey,
|
string PictureSourceKey,
|
||||||
string PictureDestinationKey,
|
string PictureDestinationKey,
|
||||||
ImageSizes Sizes);
|
ImageSizes Sizes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record UpdatePlayListPictureEvent(
|
public record UpdatePlayListPictureEvent(
|
||||||
Guid PlayListId,
|
Guid PlayListId,
|
||||||
string Bucket,
|
string Bucket,
|
||||||
string SourceKey,
|
string SourceKey,
|
||||||
ImageSizes Sizes);
|
ImageSizes Sizes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record UpdatePlayListPictureSourceEvent(
|
public record UpdatePlayListPictureSourceEvent(
|
||||||
Guid PlayListId,
|
Guid PlayListId,
|
||||||
Guid PictureIntentId,
|
Guid PictureIntentId,
|
||||||
@@ -9,3 +9,4 @@ public record UpdatePlayListPictureSourceEvent(
|
|||||||
string PictureSourceKey,
|
string PictureSourceKey,
|
||||||
string PictureDestinationKey,
|
string PictureDestinationKey,
|
||||||
ImageSizes Sizes);
|
ImageSizes Sizes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record UpdateTrackAudioEvent(
|
public record UpdateTrackAudioEvent(
|
||||||
Guid TrackId,
|
Guid TrackId,
|
||||||
string SourceBucket,
|
string SourceBucket,
|
||||||
string SourceKey,
|
string SourceKey,
|
||||||
string DestinationBucket,
|
string DestinationBucket,
|
||||||
string DestinationFolderKey);
|
string DestinationFolderKey);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Events;
|
namespace Musify.Application.Events
|
||||||
|
{
|
||||||
public record UpdateTrackPictureEvent(
|
public record UpdateTrackPictureEvent(
|
||||||
Guid TrackId,
|
Guid TrackId,
|
||||||
string Bucket,
|
string Bucket,
|
||||||
string SourceKey,
|
string SourceKey,
|
||||||
ImageSizes Sizes);
|
ImageSizes Sizes);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using Mediator;
|
using Mediator;
|
||||||
using Musify.Domain.ValueObjects;
|
|
||||||
using Musify.Application.Genres.Responses;
|
using Musify.Application.Genres.Responses;
|
||||||
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Genres;
|
namespace Musify.Application.Genres
|
||||||
|
{
|
||||||
public record GetAvailableGenresQuery : IQuery<IReadOnlyList<AvailableGenreResponse>>;
|
public record GetAvailableGenresQuery : IQuery<IReadOnlyList<AvailableGenreResponse>>;
|
||||||
|
|
||||||
public class GetAvailableGenresQueryHandler
|
public class GetAvailableGenresQueryHandler
|
||||||
@@ -22,3 +22,4 @@ public class GetAvailableGenresQueryHandler
|
|||||||
return ValueTask.FromResult(result);
|
return ValueTask.FromResult(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using Mediator;
|
using Mediator;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Musify.Application.Contracts;
|
using Musify.Application.Contracts;
|
||||||
using Musify.Domain.ValueObjects;
|
|
||||||
using Musify.Application.Genres.Responses;
|
using Musify.Application.Genres.Responses;
|
||||||
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Genres;
|
namespace Musify.Application.Genres
|
||||||
|
{
|
||||||
public record GetGenresQuery : IQuery<IReadOnlyList<GenreResponse>>;
|
public record GetGenresQuery : IQuery<IReadOnlyList<GenreResponse>>;
|
||||||
|
|
||||||
public class GetGenresQueryHandler(IDatabase database)
|
public class GetGenresQueryHandler(IDatabase database)
|
||||||
@@ -27,3 +27,4 @@ public class GetGenresQueryHandler(IDatabase database)
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Genres.Responses;
|
namespace Musify.Application.Genres.Responses
|
||||||
|
{
|
||||||
public record AvailableGenreResponse(Genre Genre, IReadOnlyList<Genre> IncompatibleWith);
|
public record AvailableGenreResponse(Genre Genre, IReadOnlyList<Genre> IncompatibleWith);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Genres.Responses;
|
namespace Musify.Application.Genres.Responses
|
||||||
|
{
|
||||||
public record GenreResponse(Genre Genre, int TrackCount);
|
public record GenreResponse(Genre Genre, int TrackCount);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using Musify.Application.Shared;
|
|||||||
using Musify.Application.Tracks.Responses;
|
using Musify.Application.Tracks.Responses;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Likes;
|
namespace Musify.Application.Likes
|
||||||
|
{
|
||||||
public record GetLikedTracksQuery(
|
public record GetLikedTracksQuery(
|
||||||
long UserId,
|
long UserId,
|
||||||
int PageNumber,
|
int PageNumber,
|
||||||
@@ -28,3 +28,4 @@ public class GetLikedTracksQueryHandler(IDatabase database)
|
|||||||
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ using Musify.Application.Shared;
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Likes;
|
namespace Musify.Application.Likes
|
||||||
|
{
|
||||||
public record ToggleTrackLikeCommand(long UserId, Guid TrackId)
|
public record ToggleTrackLikeCommand(long UserId, Guid TrackId)
|
||||||
: ICommand<ErrorOr<bool>>;
|
: ICommand<ErrorOr<bool>>;
|
||||||
|
|
||||||
@@ -56,3 +56,4 @@ public class ToggleTrackLikeCommandHandler(
|
|||||||
return nowLiked;
|
return nowLiked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ using Musify.Application.Contracts;
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Mixes;
|
namespace Musify.Application.Mixes
|
||||||
|
{
|
||||||
public record GenerateMixesForUserCommand(long UserId)
|
public record GenerateMixesForUserCommand(long UserId)
|
||||||
: ICommand;
|
: ICommand;
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ public class GenerateMixesForUserCommandHandler(
|
|||||||
private static List<Guid> Shuffle(IEnumerable<Guid> trackIds) =>
|
private static List<Guid> Shuffle(IEnumerable<Guid> trackIds) =>
|
||||||
[.. trackIds.OrderBy(_ => Guid.NewGuid())];
|
[.. trackIds.OrderBy(_ => Guid.NewGuid())];
|
||||||
|
|
||||||
private static List<Guid> Interleave(IReadOnlyList<Guid> first, IReadOnlyList<Guid> second)
|
private static List<Guid> Interleave(List<Guid> first, List<Guid> second)
|
||||||
{
|
{
|
||||||
var merged = new List<Guid>(first.Count + second.Count);
|
var merged = new List<Guid>(first.Count + second.Count);
|
||||||
for (var index = 0; index < Math.Max(first.Count, second.Count); index++)
|
for (var index = 0; index < Math.Max(first.Count, second.Count); index++)
|
||||||
@@ -128,3 +128,4 @@ public class GenerateMixesForUserCommandHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using Musify.Application.Contracts;
|
|||||||
using Musify.Application.Mixes.Responses;
|
using Musify.Application.Mixes.Responses;
|
||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.Mixes;
|
namespace Musify.Application.Mixes
|
||||||
|
{
|
||||||
public record GetMixByIdQuery(long UserId, Guid MixId)
|
public record GetMixByIdQuery(long UserId, Guid MixId)
|
||||||
: IQuery<ErrorOr<MixApplicationResponse>>;
|
: IQuery<ErrorOr<MixApplicationResponse>>;
|
||||||
|
|
||||||
@@ -29,3 +29,4 @@ public class GetMixByIdQueryHandler(IDatabase database)
|
|||||||
return new MixApplicationResponse(mix.Id, mix.Kind, items.Count, items);
|
return new MixApplicationResponse(mix.Id, mix.Kind, items.Count, items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Musify.Application.Contracts;
|
using Musify.Application.Contracts;
|
||||||
using Musify.Application.Mixes.Responses;
|
using Musify.Application.Mixes.Responses;
|
||||||
|
|
||||||
namespace Musify.Application.Mixes;
|
namespace Musify.Application.Mixes
|
||||||
|
{
|
||||||
public record GetMixesByUserIdQuery(long UserId)
|
public record GetMixesByUserIdQuery(long UserId)
|
||||||
: IQuery<IReadOnlyList<MixApplicationResponse>>;
|
: IQuery<IReadOnlyList<MixApplicationResponse>>;
|
||||||
|
|
||||||
@@ -48,3 +48,4 @@ public class GetMixesByUserIdQueryHandler(IDatabase database)
|
|||||||
return responses;
|
return responses;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ using Musify.Application.Mixes.Responses;
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Mixes;
|
namespace Musify.Application.Mixes
|
||||||
|
{
|
||||||
|
|
||||||
internal static class MixItemMapper
|
internal static class MixItemMapper
|
||||||
{
|
{
|
||||||
@@ -38,3 +38,4 @@ internal static class MixItemMapper
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Mixes.Responses;
|
namespace Musify.Application.Mixes.Responses
|
||||||
|
{
|
||||||
public record MixApplicationResponse(
|
public record MixApplicationResponse(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
MixKind Kind,
|
MixKind Kind,
|
||||||
@@ -10,3 +10,4 @@ public record MixApplicationResponse(
|
|||||||
{
|
{
|
||||||
public const int CoverItemCount = 4;
|
public const int CoverItemCount = 4;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
namespace Musify.Application.Mixes.Responses;
|
namespace Musify.Application.Mixes.Responses
|
||||||
|
{
|
||||||
public record MixItemApplicationResponse(
|
public record MixItemApplicationResponse(
|
||||||
Guid TrackId,
|
Guid TrackId,
|
||||||
string Title,
|
string Title,
|
||||||
string? Artist,
|
string? Artist,
|
||||||
double DurationSeconds,
|
double DurationSeconds,
|
||||||
int ListensCount);
|
int ListensCount);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ using Musify.Application.Contracts;
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Pictures;
|
namespace Musify.Application.Pictures
|
||||||
|
{
|
||||||
public enum CoverOwner
|
public enum CoverOwner
|
||||||
{
|
{
|
||||||
Track,
|
Track,
|
||||||
@@ -73,3 +73,4 @@ public class GetCoverQueryHandler(
|
|||||||
return new CoverLocation(storageConfiguration.Bucket, key, MimeUtility.GetMimeMapping(name));
|
return new CoverLocation(storageConfiguration.Bucket, key, MimeUtility.GetMimeMapping(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using Musify.Application.Shared;
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Pictures;
|
namespace Musify.Application.Pictures
|
||||||
|
{
|
||||||
/// <summary>What an album or playlist needs to switch to a freshly uploaded picture.</summary>
|
/// <summary>What an album or playlist needs to switch to a freshly uploaded picture.</summary>
|
||||||
public sealed record PreparedPicture(UploadIntent Intent, EntityPictures Pictures, string FinalKey, ImageSizes Sizes);
|
public sealed record PreparedPicture(UploadIntent Intent, EntityPictures Pictures, string FinalKey, ImageSizes Sizes);
|
||||||
|
|
||||||
@@ -33,3 +33,4 @@ public static class PictureSourceChange
|
|||||||
owner.PicturesSizes.ToImageSizes(owner.Routes));
|
owner.PicturesSizes.ToImageSizes(owner.Routes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
namespace Musify.Application.Pictures.Responses;
|
namespace Musify.Application.Pictures.Responses
|
||||||
|
{
|
||||||
public record PictureUploadResponse(
|
public record PictureUploadResponse(
|
||||||
Guid IntentId,
|
Guid IntentId,
|
||||||
string Bucket,
|
string Bucket,
|
||||||
@@ -8,3 +8,4 @@ public record PictureUploadResponse(
|
|||||||
string ContentType,
|
string ContentType,
|
||||||
int ExpiresInSeconds,
|
int ExpiresInSeconds,
|
||||||
string UploadUrl);
|
string UploadUrl);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ using Musify.Application.Shared;
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.PlayLists;
|
namespace Musify.Application.PlayLists
|
||||||
|
{
|
||||||
/// <summary>Appends tracks to a playlist in the given order, skipping the ones it already has.</summary>
|
/// <summary>Appends tracks to a playlist in the given order, skipping the ones it already has.</summary>
|
||||||
public record AddTracksToPlayListCommand(long UserId, Guid PlayListId, IReadOnlyList<Guid> TrackIds)
|
public record AddTracksToPlayListCommand(long UserId, Guid PlayListId, IReadOnlyList<Guid> TrackIds)
|
||||||
: ICommand<ErrorOr<Success>>;
|
: ICommand<ErrorOr<Success>>;
|
||||||
@@ -71,3 +71,4 @@ public class AddTracksToPlayListCommandHandler(
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ using Musify.Application.Shared;
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.PlayLists;
|
namespace Musify.Application.PlayLists
|
||||||
|
{
|
||||||
public record CreatePlayListCommand(
|
public record CreatePlayListCommand(
|
||||||
long UserId,
|
long UserId,
|
||||||
string Name,
|
string Name,
|
||||||
@@ -76,3 +76,4 @@ public class CreatePlayListCommandHandler(
|
|||||||
return PlayListApplicationResponse.FromEntity(playList);
|
return PlayListApplicationResponse.FromEntity(playList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ using Musify.Application.Events;
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.PlayLists;
|
namespace Musify.Application.PlayLists
|
||||||
|
{
|
||||||
public record DeletePlayListCommand(long UserId, Guid PlayListId)
|
public record DeletePlayListCommand(long UserId, Guid PlayListId)
|
||||||
: ICommand<ErrorOr<Success>>;
|
: ICommand<ErrorOr<Success>>;
|
||||||
|
|
||||||
@@ -34,3 +34,4 @@ public class DeletePlayListCommandHandler(
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using Musify.Application.Contracts;
|
|||||||
using Musify.Application.PlayLists.Responses;
|
using Musify.Application.PlayLists.Responses;
|
||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.PlayLists;
|
namespace Musify.Application.PlayLists
|
||||||
|
{
|
||||||
public record GetPlayListByIdQuery(Guid Id, long? RequestingUserId = null)
|
public record GetPlayListByIdQuery(Guid Id, long? RequestingUserId = null)
|
||||||
: IQuery<ErrorOr<PlayListApplicationResponse>>;
|
: IQuery<ErrorOr<PlayListApplicationResponse>>;
|
||||||
|
|
||||||
@@ -28,3 +28,4 @@ public class GetPlayListByIdQueryHandler(IDatabase database)
|
|||||||
: playList;
|
: playList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ using Musify.Application.Shared;
|
|||||||
using Musify.Application.Tracks.Responses;
|
using Musify.Application.Tracks.Responses;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.PlayLists;
|
namespace Musify.Application.PlayLists
|
||||||
|
{
|
||||||
public record GetPlayListTracksQuery(
|
public record GetPlayListTracksQuery(
|
||||||
Guid PlayListId,
|
Guid PlayListId,
|
||||||
int PageNumber,
|
int PageNumber,
|
||||||
@@ -39,3 +39,4 @@ public class GetPlayListTracksQueryHandler(IDatabase database)
|
|||||||
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ using Musify.Application.Contracts;
|
|||||||
using Musify.Application.PlayLists.Responses;
|
using Musify.Application.PlayLists.Responses;
|
||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.PlayLists;
|
namespace Musify.Application.PlayLists
|
||||||
|
{
|
||||||
public record GetPlayListsByUserIdQuery(
|
public record GetPlayListsByUserIdQuery(
|
||||||
long UserId,
|
long UserId,
|
||||||
string? Name,
|
string? Name,
|
||||||
@@ -38,3 +38,4 @@ public class GetPlayListsByUserIdQueryHandler(IDatabase database)
|
|||||||
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
.ToPaginatedAsync(new PageRequest(request.PageNumber, request.PageSize), cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using Microsoft.Extensions.Logging;
|
|||||||
using Musify.Application.Contracts;
|
using Musify.Application.Contracts;
|
||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
|
||||||
namespace Musify.Application.PlayLists;
|
namespace Musify.Application.PlayLists
|
||||||
|
{
|
||||||
public record RemoveTrackFromPlayListCommand(long UserId, Guid PlayListId, Guid TrackId)
|
public record RemoveTrackFromPlayListCommand(long UserId, Guid PlayListId, Guid TrackId)
|
||||||
: ICommand<ErrorOr<Success>>;
|
: ICommand<ErrorOr<Success>>;
|
||||||
|
|
||||||
@@ -43,3 +43,4 @@ public class RemoveTrackFromPlayListCommandHandler(
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using Musify.Application.Pictures.Responses;
|
|||||||
using Musify.Application.Services;
|
using Musify.Application.Services;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.PlayLists;
|
namespace Musify.Application.PlayLists
|
||||||
|
{
|
||||||
public record RequestPlayListPictureUploadCommand(
|
public record RequestPlayListPictureUploadCommand(
|
||||||
long UserId,
|
long UserId,
|
||||||
string FileType,
|
string FileType,
|
||||||
@@ -22,3 +22,4 @@ public class RequestPlayListPictureUploadCommandHandler(UploadIntentIssuer issue
|
|||||||
request.UserId, UploadIntentPurpose.PlayListPicture, playListConfiguration,
|
request.UserId, UploadIntentPurpose.PlayListPicture, playListConfiguration,
|
||||||
request.FileType, request.ContentType, request.ExpectedSizeBytes, cancellationToken);
|
request.FileType, request.ContentType, request.ExpectedSizeBytes, cancellationToken);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ using Musify.Domain.Entities;
|
|||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
|
|
||||||
namespace Musify.Application.PlayLists.Responses;
|
namespace Musify.Application.PlayLists.Responses
|
||||||
|
{
|
||||||
public record PlayListApplicationResponse(
|
public record PlayListApplicationResponse(
|
||||||
Guid Id,
|
Guid Id,
|
||||||
string Name,
|
string Name,
|
||||||
@@ -43,3 +43,4 @@ public record PlayListApplicationResponse(
|
|||||||
durationSeconds);
|
durationSeconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.PlayLists.Responses;
|
namespace Musify.Application.PlayLists.Responses
|
||||||
|
{
|
||||||
public static class PlayListProjections
|
public static class PlayListProjections
|
||||||
{
|
{
|
||||||
public const int CoverTrackCount = 4;
|
public const int CoverTrackCount = 4;
|
||||||
@@ -35,3 +35,4 @@ public static class PlayListProjections
|
|||||||
.Where(playListTrack => playListTrack.Track.LifeCycleStatus == LifeCycleStatus.Active)
|
.Where(playListTrack => playListTrack.Track.LifeCycleStatus == LifeCycleStatus.Active)
|
||||||
.Sum(playListTrack => playListTrack.Track.DurationSeconds)));
|
.Sum(playListTrack => playListTrack.Track.DurationSeconds)));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ using Musify.Application.Services;
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.PlayLists;
|
namespace Musify.Application.PlayLists
|
||||||
|
{
|
||||||
public record UpdatePlayListCommand(
|
public record UpdatePlayListCommand(
|
||||||
long UserId,
|
long UserId,
|
||||||
Guid PlayListId,
|
Guid PlayListId,
|
||||||
@@ -78,3 +78,4 @@ public class UpdatePlayListCommandHandler(
|
|||||||
.SingleAsync(cancellationToken);
|
.SingleAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ using Musify.Application.Configuration;
|
|||||||
using Musify.Application.Contracts;
|
using Musify.Application.Contracts;
|
||||||
using Musify.Application.Tracks.Responses;
|
using Musify.Application.Tracks.Responses;
|
||||||
|
|
||||||
namespace Musify.Application.Services;
|
namespace Musify.Application.Services
|
||||||
|
{
|
||||||
/// <summary>Builds the stream URL and signed ticket for a processed track. It never touches the database.</summary>
|
/// <summary>Builds the stream URL and signed ticket for a processed track. It never touches the database.</summary>
|
||||||
public sealed class TrackStreamIssuer(
|
public sealed class TrackStreamIssuer(
|
||||||
IStreamTicketService ticketService,
|
IStreamTicketService ticketService,
|
||||||
@@ -33,3 +33,4 @@ public sealed class TrackStreamIssuer(
|
|||||||
return (long)playbackConfiguration.AnonymousFragmentSeconds * playbackConfiguration.EstimatedAudioBytesPerSecond;
|
return (long)playbackConfiguration.AnonymousFragmentSeconds * playbackConfiguration.EstimatedAudioBytesPerSecond;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ using Musify.Application.Shared;
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Services;
|
namespace Musify.Application.Services
|
||||||
|
{
|
||||||
public sealed record UploadRequest(
|
public sealed record UploadRequest(
|
||||||
UploadIntentPurpose Purpose,
|
UploadIntentPurpose Purpose,
|
||||||
PictureRoutes Routes,
|
PictureRoutes Routes,
|
||||||
@@ -131,3 +131,4 @@ public sealed class UploadIntentIssuer(
|
|||||||
? uploadIntentConfiguration.DefaultExpectedAudioSizeBytes
|
? uploadIntentConfiguration.DefaultExpectedAudioSizeBytes
|
||||||
: uploadIntentConfiguration.DefaultExpectedPictureSizeBytes;
|
: uploadIntentConfiguration.DefaultExpectedPictureSizeBytes;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ using Musify.Application.Shared;
|
|||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Services;
|
namespace Musify.Application.Services
|
||||||
|
{
|
||||||
public sealed class UploadIntentValidator(
|
public sealed class UploadIntentValidator(
|
||||||
IDatabase database,
|
IDatabase database,
|
||||||
IStorageService storageService,
|
IStorageService storageService,
|
||||||
@@ -75,3 +75,4 @@ public sealed class UploadIntentValidator(
|
|||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using ErrorOr;
|
using ErrorOr;
|
||||||
|
|
||||||
namespace Musify.Application.Shared;
|
namespace Musify.Application.Shared
|
||||||
|
{
|
||||||
public static class AppErrors
|
public static class AppErrors
|
||||||
{
|
{
|
||||||
public static Error NotFound(string entity, object id) =>
|
public static Error NotFound(string entity, object id) =>
|
||||||
@@ -13,3 +13,4 @@ public static class AppErrors
|
|||||||
public static Error Conflict(string code, string description) =>
|
public static Error Conflict(string code, string description) =>
|
||||||
Error.Conflict(code, description);
|
Error.Conflict(code, description);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
namespace Musify.Application.Shared;
|
namespace Musify.Application.Shared
|
||||||
|
{
|
||||||
public record ImageSize(
|
public record ImageSize(
|
||||||
string SavePath,
|
string SavePath,
|
||||||
int Width,
|
int Width,
|
||||||
@@ -9,3 +9,4 @@ public record ImageSizes(
|
|||||||
ImageSize Small,
|
ImageSize Small,
|
||||||
ImageSize Medium,
|
ImageSize Medium,
|
||||||
ImageSize Large);
|
ImageSize Large);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Musify.Domain.Abstractions;
|
using Musify.Domain.Abstractions;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Shared;
|
namespace Musify.Application.Shared
|
||||||
|
{
|
||||||
public static class OwnershipExtensions
|
public static class OwnershipExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -26,3 +26,4 @@ public static class OwnershipExtensions
|
|||||||
: AppErrors.Forbidden(typeof(T).Name, id);
|
: AppErrors.Forbidden(typeof(T).Name, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace Musify.Application.Shared;
|
namespace Musify.Application.Shared
|
||||||
|
{
|
||||||
public record PageRequest(int PageNumber = 1, int PageSize = 20)
|
public record PageRequest(int PageNumber = 1, int PageSize = 20)
|
||||||
{
|
{
|
||||||
public const int MaxPageSize = 100;
|
public const int MaxPageSize = 100;
|
||||||
@@ -43,3 +43,4 @@ public static class PaginationExtensions
|
|||||||
HasPreviousPage: pageNumber > 1 && pageCount > 0);
|
HasPreviousPage: pageNumber > 1 && pageCount > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
namespace Musify.Application.Shared;
|
namespace Musify.Application.Shared
|
||||||
|
{
|
||||||
public static class StorageKey
|
public static class StorageKey
|
||||||
{
|
{
|
||||||
public static string Combine(params string[] segments)
|
public static string Combine(params string[] segments)
|
||||||
@@ -9,3 +9,4 @@ public static class StorageKey
|
|||||||
.Select(static s => s.Trim().Trim('/', '\\')));
|
.Select(static s => s.Trim().Trim('/', '\\')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
namespace Musify.Application.Shared;
|
namespace Musify.Application.Shared
|
||||||
|
{
|
||||||
public static class TextNormalizer
|
public static class TextNormalizer
|
||||||
{
|
{
|
||||||
public static string Normalize(string value) => value.Trim().ToUpperInvariant();
|
public static string Normalize(string value) => value.Trim().ToUpperInvariant();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Musify.Application.Configuration;
|
using Musify.Application.Configuration;
|
||||||
using Musify.Application.Contracts;
|
using Musify.Application.Contracts;
|
||||||
using Musify.Application.Shared;
|
|
||||||
using Musify.Application.Events;
|
using Musify.Application.Events;
|
||||||
using Musify.Application.Services;
|
using Musify.Application.Services;
|
||||||
|
using Musify.Application.Shared;
|
||||||
using Musify.Application.Tracks.Responses;
|
using Musify.Application.Tracks.Responses;
|
||||||
using Musify.Domain.Entities;
|
using Musify.Domain.Entities;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Tracks;
|
namespace Musify.Application.Tracks
|
||||||
|
{
|
||||||
public record CreateTrackCommand(
|
public record CreateTrackCommand(
|
||||||
long UserId,
|
long UserId,
|
||||||
string Title,
|
string Title,
|
||||||
@@ -111,3 +111,4 @@ public class CreateTrackCommandHandler(
|
|||||||
return TrackApplicationResponse.FromEntity(trackEntity, listensCount: 0);
|
return TrackApplicationResponse.FromEntity(trackEntity, listensCount: 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Musify.Application.Contracts;
|
using Musify.Application.Contracts;
|
||||||
|
|
||||||
namespace Musify.Application.Tracks;
|
namespace Musify.Application.Tracks
|
||||||
|
{
|
||||||
public record DeleteStaleUncountedListensCommand : ICommand<int>;
|
public record DeleteStaleUncountedListensCommand : ICommand<int>;
|
||||||
|
|
||||||
public class DeleteStaleUncountedListensCommandHandler(
|
public class DeleteStaleUncountedListensCommandHandler(
|
||||||
@@ -30,3 +30,4 @@ public class DeleteStaleUncountedListensCommandHandler(
|
|||||||
return stale.Count;
|
return stale.Count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ using Musify.Application.Events;
|
|||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
using Musify.Domain.ValueObjects;
|
using Musify.Domain.ValueObjects;
|
||||||
|
|
||||||
namespace Musify.Application.Tracks;
|
namespace Musify.Application.Tracks
|
||||||
|
{
|
||||||
public record DeleteTrackCommand(
|
public record DeleteTrackCommand(
|
||||||
long UserId,
|
long UserId,
|
||||||
Guid TrackId)
|
Guid TrackId)
|
||||||
@@ -39,3 +39,4 @@ public class DeleteTrackCommandHandler(
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user