Update scheduling parameters and refactor channel endpoints: extend HorizonDays to 7 and RetentionDays to 90 in appsettings.json. Consolidate channel-related endpoint logic by removing obsolete files and enhancing the ShowEndpoints with audience and genre management capabilities. Improve error handling and streamline command handlers for channel operations.
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
using LiteCqrs;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.AddCollectionShow;
|
||||
|
||||
public sealed record AddCollectionShowCommand(Guid CollectionId, Guid ShowId) : ICommand<Result>;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.AddCollectionShow;
|
||||
|
||||
public sealed class AddCollectionShowCommandHandler(IAppDbContext dbContext)
|
||||
: ICommandHandler<AddCollectionShowCommand, Result>
|
||||
{
|
||||
public async Task<Result> Handle(
|
||||
AddCollectionShowCommand command,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var collection = await dbContext
|
||||
.Collections.Include(c => c.Items)
|
||||
.FirstOrDefaultAsync(c => c.Id == command.CollectionId, cancellationToken);
|
||||
if (collection is null)
|
||||
return Result.Failure(CollectionErrors.NotFound);
|
||||
|
||||
if (!await dbContext.Shows.AnyAsync(s => s.Id == command.ShowId, cancellationToken))
|
||||
return Result.Failure(ShowErrors.NotFound);
|
||||
|
||||
return collection.AddShow(command.ShowId) is null
|
||||
? Result.Failure(CollectionErrors.ShowAlreadyAdded)
|
||||
: Result.Success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using TeleWave.Domain.Library;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections;
|
||||
|
||||
/// <summary>Коллекция в списке: без состава, но со сводкой — сколько частей и сколько в них единиц
|
||||
/// воспроизведения (у сериала внутри коллекции их больше одной).</summary>
|
||||
public sealed record CollectionSummaryDto(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string? Description,
|
||||
Guid? PosterImageId,
|
||||
int ItemCount,
|
||||
int UnitCount,
|
||||
DateTimeOffset CreatedAt
|
||||
);
|
||||
|
||||
/// <summary>Позиция коллекции с данными шоу — чтобы список правился без второго запроса.</summary>
|
||||
public sealed record CollectionItemDto(
|
||||
Guid ShowId,
|
||||
int Position,
|
||||
string ShowName,
|
||||
ShowKind ShowKind,
|
||||
ShowAudience ShowAudience,
|
||||
int EpisodeCount,
|
||||
int? Year,
|
||||
Guid? PosterImageId
|
||||
);
|
||||
|
||||
public sealed record CollectionDto(
|
||||
Guid Id,
|
||||
string Name,
|
||||
string? Description,
|
||||
Guid? PosterImageId,
|
||||
DateTimeOffset CreatedAt,
|
||||
IReadOnlyList<CollectionItemDto> Items
|
||||
);
|
||||
|
||||
/// <summary>Коллекция, в которую входит шоу — для блока «входит в коллекции» на экране шоу.</summary>
|
||||
public sealed record ShowCollectionRefDto(Guid Id, string Name, int Position);
|
||||
@@ -0,0 +1,21 @@
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections;
|
||||
|
||||
public static class CollectionErrors
|
||||
{
|
||||
public static readonly Error NotFound = Error.NotFound(
|
||||
"Collections.NotFound",
|
||||
"Коллекция не найдена."
|
||||
);
|
||||
|
||||
public static readonly Error ShowAlreadyAdded = Error.Conflict(
|
||||
"Collections.ShowAlreadyAdded",
|
||||
"Это шоу уже входит в коллекцию."
|
||||
);
|
||||
|
||||
public static readonly Error ShowNotInCollection = Error.NotFound(
|
||||
"Collections.ShowNotInCollection",
|
||||
"Шоу не входит в коллекцию."
|
||||
);
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
using LiteCqrs;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.CreateCollection;
|
||||
|
||||
public sealed record CreateCollectionCommand(string Name, string? Description = null)
|
||||
: ICommand<Result<Guid>>;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using LiteCqrs;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Common.Models;
|
||||
using TeleWave.Domain.Library;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.CreateCollection;
|
||||
|
||||
public sealed class CreateCollectionCommandHandler(IAppDbContext dbContext)
|
||||
: ICommandHandler<CreateCollectionCommand, Result<Guid>>
|
||||
{
|
||||
public Task<Result<Guid>> Handle(
|
||||
CreateCollectionCommand command,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var collection = Collection.Create(command.Name, command.Description);
|
||||
dbContext.Collections.Add(collection);
|
||||
return Task.FromResult(Result.Success(collection.Id));
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.CreateCollection;
|
||||
|
||||
public sealed class CreateCollectionCommandValidator : AbstractValidator<CreateCollectionCommand>
|
||||
{
|
||||
public CreateCollectionCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(256);
|
||||
RuleFor(x => x.Description).MaximumLength(2048);
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
using LiteCqrs;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.DeleteCollection;
|
||||
|
||||
public sealed record DeleteCollectionCommand(Guid CollectionId) : ICommand<Result>;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Common.Models;
|
||||
using TeleWave.Application.Programming.Groups;
|
||||
using TeleWave.Domain.Programming;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.DeleteCollection;
|
||||
|
||||
public sealed class DeleteCollectionCommandHandler(
|
||||
IAppDbContext dbContext,
|
||||
GroupMembershipCleaner groupCleaner
|
||||
) : ICommandHandler<DeleteCollectionCommand, Result>
|
||||
{
|
||||
public async Task<Result> Handle(
|
||||
DeleteCollectionCommand command,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var collection = await dbContext.Collections.FirstOrDefaultAsync(
|
||||
c => c.Id == command.CollectionId,
|
||||
cancellationToken
|
||||
);
|
||||
if (collection is null)
|
||||
return Result.Failure(CollectionErrors.NotFound);
|
||||
|
||||
// Ссылка группы полиморфна — каскад БД её не снимет.
|
||||
await groupCleaner.RemoveElementAsync(
|
||||
GroupElementKind.Collection,
|
||||
command.CollectionId,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
dbContext.Collections.Remove(collection);
|
||||
return Result.Success();
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
using LiteCqrs;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.GetCollection;
|
||||
|
||||
public sealed record GetCollectionQuery(Guid Id) : IQuery<Result<CollectionDto>>;
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.GetCollection;
|
||||
|
||||
public sealed class GetCollectionQueryHandler(IAppDbContext dbContext)
|
||||
: IQueryHandler<GetCollectionQuery, Result<CollectionDto>>
|
||||
{
|
||||
public async Task<Result<CollectionDto>> Handle(
|
||||
GetCollectionQuery query,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var collection = await dbContext
|
||||
.Collections.AsNoTracking()
|
||||
.Include(c => c.Items)
|
||||
.FirstOrDefaultAsync(c => c.Id == query.Id, cancellationToken);
|
||||
if (collection is null)
|
||||
return Result.Failure<CollectionDto>(CollectionErrors.NotFound);
|
||||
|
||||
var showIds = collection.Items.Select(i => i.ShowId).ToList();
|
||||
var shows = await dbContext
|
||||
.Shows.AsNoTracking()
|
||||
.Where(s => showIds.Contains(s.Id))
|
||||
.Select(s => new
|
||||
{
|
||||
s.Id,
|
||||
s.Name,
|
||||
s.Kind,
|
||||
s.Audience,
|
||||
s.Year,
|
||||
s.PosterImageId,
|
||||
EpisodeCount = s.Episodes.Count,
|
||||
})
|
||||
.ToDictionaryAsync(s => s.Id, cancellationToken);
|
||||
|
||||
var items = collection
|
||||
.Items.OrderBy(i => i.Position)
|
||||
.Select(i =>
|
||||
{
|
||||
shows.TryGetValue(i.ShowId, out var show);
|
||||
return new CollectionItemDto(
|
||||
i.ShowId,
|
||||
i.Position,
|
||||
show?.Name ?? "—",
|
||||
show?.Kind ?? default,
|
||||
show?.Audience ?? default,
|
||||
show?.EpisodeCount ?? 0,
|
||||
show?.Year,
|
||||
show?.PosterImageId
|
||||
);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return Result.Success(
|
||||
new CollectionDto(
|
||||
collection.Id,
|
||||
collection.Name,
|
||||
collection.Description,
|
||||
collection.PosterImageId,
|
||||
collection.CreatedAt,
|
||||
items
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
using LiteCqrs;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.ListCollections;
|
||||
|
||||
public sealed record ListCollectionsQuery : IQuery<IReadOnlyList<CollectionSummaryDto>>;
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.ListCollections;
|
||||
|
||||
public sealed class ListCollectionsQueryHandler(IAppDbContext dbContext)
|
||||
: IQueryHandler<ListCollectionsQuery, IReadOnlyList<CollectionSummaryDto>>
|
||||
{
|
||||
public async Task<IReadOnlyList<CollectionSummaryDto>> Handle(
|
||||
ListCollectionsQuery query,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var collections = await dbContext
|
||||
.Collections.AsNoTracking()
|
||||
.Include(c => c.Items)
|
||||
.OrderBy(c => c.Name)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// Единиц воспроизведения может быть больше, чем частей: сериал внутри коллекции
|
||||
// разворачивается в свои серии.
|
||||
var showIds = collections.SelectMany(c => c.Items.Select(i => i.ShowId)).Distinct().ToList();
|
||||
var episodeCounts = await dbContext
|
||||
.Shows.AsNoTracking()
|
||||
.Where(s => showIds.Contains(s.Id))
|
||||
.Select(s => new { s.Id, Count = s.Episodes.Count })
|
||||
.ToDictionaryAsync(x => x.Id, x => x.Count, cancellationToken);
|
||||
|
||||
return collections
|
||||
.Select(c => new CollectionSummaryDto(
|
||||
c.Id,
|
||||
c.Name,
|
||||
c.Description,
|
||||
c.PosterImageId,
|
||||
c.Items.Count,
|
||||
c.Items.Sum(i => episodeCounts.TryGetValue(i.ShowId, out var n) ? n : 0),
|
||||
c.CreatedAt
|
||||
))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
using LiteCqrs;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.RemoveCollectionShow;
|
||||
|
||||
public sealed record RemoveCollectionShowCommand(Guid CollectionId, Guid ShowId) : ICommand<Result>;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.RemoveCollectionShow;
|
||||
|
||||
public sealed class RemoveCollectionShowCommandHandler(IAppDbContext dbContext)
|
||||
: ICommandHandler<RemoveCollectionShowCommand, Result>
|
||||
{
|
||||
public async Task<Result> Handle(
|
||||
RemoveCollectionShowCommand command,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var collection = await dbContext
|
||||
.Collections.Include(c => c.Items)
|
||||
.FirstOrDefaultAsync(c => c.Id == command.CollectionId, cancellationToken);
|
||||
if (collection is null)
|
||||
return Result.Failure(CollectionErrors.NotFound);
|
||||
|
||||
return collection.RemoveShow(command.ShowId)
|
||||
? Result.Success()
|
||||
: Result.Failure(CollectionErrors.ShowNotInCollection);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using LiteCqrs;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.ReorderCollection;
|
||||
|
||||
/// <summary>Переставляет части коллекции в порядке <paramref name="ShowIdsInOrder"/>. Не упомянутые
|
||||
/// остаются после них, сохраняя относительный порядок.</summary>
|
||||
public sealed record ReorderCollectionCommand(Guid CollectionId, IReadOnlyList<Guid> ShowIdsInOrder)
|
||||
: ICommand<Result>;
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.ReorderCollection;
|
||||
|
||||
public sealed class ReorderCollectionCommandHandler(IAppDbContext dbContext)
|
||||
: ICommandHandler<ReorderCollectionCommand, Result>
|
||||
{
|
||||
public async Task<Result> Handle(
|
||||
ReorderCollectionCommand command,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var collection = await dbContext
|
||||
.Collections.Include(c => c.Items)
|
||||
.FirstOrDefaultAsync(c => c.Id == command.CollectionId, cancellationToken);
|
||||
if (collection is null)
|
||||
return Result.Failure(CollectionErrors.NotFound);
|
||||
|
||||
collection.Reorder(command.ShowIdsInOrder);
|
||||
return Result.Success();
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
using LiteCqrs;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.SetCollectionPoster;
|
||||
|
||||
/// <summary>Привязать/снять постер коллекции (<paramref name="ImageId"/> = null — отвязать).</summary>
|
||||
public sealed record SetCollectionPosterCommand(Guid CollectionId, Guid? ImageId) : ICommand<Result>;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Common.Models;
|
||||
using TeleWave.Application.Images;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.SetCollectionPoster;
|
||||
|
||||
public sealed class SetCollectionPosterCommandHandler(IAppDbContext dbContext)
|
||||
: ICommandHandler<SetCollectionPosterCommand, Result>
|
||||
{
|
||||
public async Task<Result> Handle(
|
||||
SetCollectionPosterCommand command,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var collection = await dbContext.Collections.FirstOrDefaultAsync(
|
||||
c => c.Id == command.CollectionId,
|
||||
cancellationToken
|
||||
);
|
||||
if (collection is null)
|
||||
return Result.Failure(CollectionErrors.NotFound);
|
||||
|
||||
if (
|
||||
command.ImageId is { } imageId
|
||||
&& !await dbContext.Images.AnyAsync(i => i.Id == imageId, cancellationToken)
|
||||
)
|
||||
return Result.Failure(ImageErrors.NotFound);
|
||||
|
||||
collection.SetPosterImage(command.ImageId);
|
||||
return Result.Success();
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
using LiteCqrs;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.UpdateCollection;
|
||||
|
||||
public sealed record UpdateCollectionCommand(Guid CollectionId, string Name, string? Description)
|
||||
: ICommand<Result>;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.UpdateCollection;
|
||||
|
||||
public sealed class UpdateCollectionCommandHandler(IAppDbContext dbContext)
|
||||
: ICommandHandler<UpdateCollectionCommand, Result>
|
||||
{
|
||||
public async Task<Result> Handle(
|
||||
UpdateCollectionCommand command,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var collection = await dbContext.Collections.FirstOrDefaultAsync(
|
||||
c => c.Id == command.CollectionId,
|
||||
cancellationToken
|
||||
);
|
||||
if (collection is null)
|
||||
return Result.Failure(CollectionErrors.NotFound);
|
||||
|
||||
collection.Rename(command.Name, command.Description);
|
||||
return Result.Success();
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace TeleWave.Application.Library.Collections.UpdateCollection;
|
||||
|
||||
public sealed class UpdateCollectionCommandValidator : AbstractValidator<UpdateCollectionCommand>
|
||||
{
|
||||
public UpdateCollectionCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(256);
|
||||
RuleFor(x => x.Description).MaximumLength(2048);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user