Added initial voice work search provider logic.
This commit is contained in:
8
JSMR.Application/Common/AIGeneration.cs
Normal file
8
JSMR.Application/Common/AIGeneration.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace JSMR.Application.Common;
|
||||||
|
|
||||||
|
public enum AIGeneration
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Partial = 1,
|
||||||
|
Full = 2
|
||||||
|
}
|
||||||
8
JSMR.Application/Common/AgeRating.cs
Normal file
8
JSMR.Application/Common/AgeRating.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace JSMR.Application.Common;
|
||||||
|
|
||||||
|
public enum AgeRating
|
||||||
|
{
|
||||||
|
AllAges = 1,
|
||||||
|
R15 = 2,
|
||||||
|
R18 = 3
|
||||||
|
}
|
||||||
10
JSMR.Application/Common/Locale.cs
Normal file
10
JSMR.Application/Common/Locale.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace JSMR.Application.Common;
|
||||||
|
|
||||||
|
public enum Locale
|
||||||
|
{
|
||||||
|
Japanese,
|
||||||
|
English,
|
||||||
|
ChineseSimplified,
|
||||||
|
ChineseTraditional,
|
||||||
|
Korean
|
||||||
|
}
|
||||||
9
JSMR.Application/Common/VoiceWorkStatus.cs
Normal file
9
JSMR.Application/Common/VoiceWorkStatus.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Application.Common;
|
||||||
|
|
||||||
|
public enum VoiceWorkStatus
|
||||||
|
{
|
||||||
|
Available = 0,
|
||||||
|
Upcoming = 1,
|
||||||
|
NewRelease = 2,
|
||||||
|
NewAndUpcoming = 3
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using JSMR.Application.Tags.Commands.SetEnglishName;
|
using JSMR.Application.Tags.Commands.SetEnglishName;
|
||||||
using JSMR.Application.Tags.Commands.UpdateTagStatus;
|
using JSMR.Application.Tags.Commands.UpdateTagStatus;
|
||||||
using JSMR.Application.VoiceWorks.Search;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace JSMR.Application.DI;
|
namespace JSMR.Application.DI;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using JSMR.Application.Common.Search;
|
using JSMR.Application.Common.Search;
|
||||||
using JSMR.Application.VoiceWorks.Search.Contracts;
|
|
||||||
|
|
||||||
namespace JSMR.Application.VoiceWorks.Ports;
|
namespace JSMR.Application.VoiceWorks.Ports;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
|
public enum CircleStatus
|
||||||
|
{
|
||||||
|
NotBlacklisted,
|
||||||
|
Favorited,
|
||||||
|
Blacklisted
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
|
public enum CreatorStatus
|
||||||
|
{
|
||||||
|
NotBlacklisted = 1,
|
||||||
|
FavoriteExcludeBlacklist = 2,
|
||||||
|
FavoriteIncludeBlacklist = 3,
|
||||||
|
Blacklisted = 4,
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
|
public interface IVoiceWorkSearchProvider : ISearchProvider<VoiceWorkSearchResult, VoiceWorkSearchCriteria, VoiceWorkSortField>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
7
JSMR.Application/VoiceWorks/Queries/Search/SaleStatus.cs
Normal file
7
JSMR.Application/VoiceWorks/Queries/Search/SaleStatus.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
|
public enum SaleStatus
|
||||||
|
{
|
||||||
|
Available = 0,
|
||||||
|
Upcoming = 2,
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
using JSMR.Application.Common.Caching;
|
using JSMR.Application.Common.Caching;
|
||||||
using JSMR.Application.VoiceWorks.Ports;
|
using JSMR.Application.VoiceWorks.Ports;
|
||||||
using JSMR.Application.VoiceWorks.Search.Contracts;
|
|
||||||
|
|
||||||
namespace JSMR.Application.VoiceWorks.Search;
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
//public sealed class SearchVoiceWorksHandler(IVoiceWorkReader reader, ICache cache)
|
//public sealed class SearchVoiceWorksHandler(IVoiceWorkReader reader, ICache cache)
|
||||||
//{
|
//{
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using JSMR.Application.Common.Search;
|
using JSMR.Application.Common.Search;
|
||||||
using JSMR.Application.VoiceWorks.Search.Contracts;
|
|
||||||
|
|
||||||
namespace JSMR.Application.VoiceWorks.Search;
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
public sealed record SearchVoiceWorksRequest(SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField> Options);
|
public sealed record SearchVoiceWorksRequest(SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField> Options);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
|
public sealed record SearchVoiceWorksResponse(VoiceWorkSearchResults Results);
|
||||||
9
JSMR.Application/VoiceWorks/Queries/Search/TagStatus.cs
Normal file
9
JSMR.Application/VoiceWorks/Queries/Search/TagStatus.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
|
public enum TagStatus
|
||||||
|
{
|
||||||
|
NotBlacklisted = 1,
|
||||||
|
FavoriteExcludeBlacklist = 2,
|
||||||
|
FavoriteIncludeBlacklist = 3,
|
||||||
|
Blacklisted = 4,
|
||||||
|
}
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
using JSMR.Application.Common;
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
namespace JSMR.Application.VoiceWorks.Search.Contracts;
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
public class VoiceWorkSearchCriteria
|
public class VoiceWorkSearchCriteria
|
||||||
{
|
{
|
||||||
public string? Keywords { get; init; }
|
public string? Keywords { get; init; }
|
||||||
public string? Title { get; init; }
|
public string? Title { get; init; }
|
||||||
public string? Circle { get; init; }
|
public string? Circle { get; init; }
|
||||||
//public SaleStatus SaleStatus { get; init; }
|
public SaleStatus? SaleStatus { get; init; }
|
||||||
//public CircleStatus CircleStatus { get; init; }
|
public CircleStatus? CircleStatus { get; init; }
|
||||||
//public TagStatus TagStatus { get; init; }
|
public TagStatus? TagStatus { get; init; }
|
||||||
//public CreatorStatus CreatorStatus { get; init; }
|
public CreatorStatus? CreatorStatus { get; init; }
|
||||||
public int[] TagIds { get; init; } = [];
|
public int[] TagIds { get; init; } = [];
|
||||||
public bool IncludeAllTags { get; init; }
|
public bool IncludeAllTags { get; init; }
|
||||||
public int[] CreatorIds { get; init; } = [];
|
public int[] CreatorIds { get; init; } = [];
|
||||||
public bool IncludeAllCreators { get; init; }
|
public bool IncludeAllCreators { get; init; }
|
||||||
//public VoiceWorkSort Sort { get; init; }
|
public Locale Locale { get; init; } = Locale.Japanese;
|
||||||
//public VoiceWorkLanguage Language { get; init; }
|
|
||||||
public DateTime? ReleaseDateStart { get; init; }
|
public DateTime? ReleaseDateStart { get; init; }
|
||||||
public DateTime? ReleaseDateEnd { get; init; }
|
public DateTime? ReleaseDateEnd { get; init; }
|
||||||
//public List<AgeRating> AgeRatings { get; init; }
|
public AgeRating[] AgeRatings { get; init; } = [];
|
||||||
public Language[] SupportedLanguages { get; init; } = [];
|
public Language[] SupportedLanguages { get; init; } = [];
|
||||||
//public List<AIGeneration> AIGenerationOptions { get; init; }
|
public AIGeneration[] AIGenerationOptions { get; init; } = [];
|
||||||
public bool ShowFavoriteVoiceWorks { get; init; }
|
public bool ShowFavoriteVoiceWorks { get; init; }
|
||||||
public bool ShowInvalidVoiceWorks { get; init; }
|
public bool ShowInvalidVoiceWorks { get; init; }
|
||||||
public int? MinDownloads { get; init; }
|
public int? MinDownloads { get; init; }
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using JSMR.Application.Common.Search;
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
namespace JSMR.Application.VoiceWorks.Search.Contracts;
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
//public record VoiceWorkSearchOptions : SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>
|
//public record VoiceWorkSearchOptions : SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>
|
||||||
//{
|
//{
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using JSMR.Application.Common;
|
||||||
|
|
||||||
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
|
public record VoiceWorkSearchResult
|
||||||
|
{
|
||||||
|
public int VoiceWorkId { get; init; }
|
||||||
|
public required string ProductId { get; init; }
|
||||||
|
public string? OriginalProductId { get; init; }
|
||||||
|
public string? Description { get; init; }
|
||||||
|
public required string ProductName { get; init; }
|
||||||
|
public required string ProductUrl { get; init; }
|
||||||
|
public bool HasImage { get; init; }
|
||||||
|
public required string Maker { get; init; }
|
||||||
|
public required string MakerId { get; init; }
|
||||||
|
public DateTime? ExpectedDate { get; init; }
|
||||||
|
public DateTime? SalesDate { get; init; }
|
||||||
|
public DateTime? PlannedReleaseDate { get; init; }
|
||||||
|
public int? Downloads { get; init; }
|
||||||
|
public int? WishlistCount { get; init; }
|
||||||
|
public byte? StarRating { get; init; }
|
||||||
|
public int? Votes { get; init; }
|
||||||
|
public bool HasTrial { get; init; }
|
||||||
|
public bool HasChobit { get; init; }
|
||||||
|
public AgeRating Rating { get; init; }
|
||||||
|
public bool Favorite { get; init; }
|
||||||
|
public byte Status { get; init; }
|
||||||
|
public byte SubtitleLanguage { get; init; }
|
||||||
|
public bool? IsValid { get; init; }
|
||||||
|
public VoiceWorkTagItem[] Tags { get; set; } = [];
|
||||||
|
public VoiceWorkCreatorItem[] Creators { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VoiceWorkTagItem
|
||||||
|
{
|
||||||
|
public int TagId { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VoiceWorkCreatorItem
|
||||||
|
{
|
||||||
|
public int CreatorId { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
|
||||||
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
|
public record VoiceWorkSearchResults : SearchResult<VoiceWorkSearchResult>
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
|
||||||
|
public enum VoiceWorkSortField
|
||||||
|
{
|
||||||
|
ReleaseDate,
|
||||||
|
Downloads,
|
||||||
|
WishlistCount,
|
||||||
|
SalesToWishlistRatio,
|
||||||
|
StarRating
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using JSMR.Application.Common.Search;
|
|
||||||
|
|
||||||
namespace JSMR.Application.VoiceWorks.Search.Contracts;
|
|
||||||
|
|
||||||
public record VoiceWorkSearchResults : SearchResult<object>
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace JSMR.Application.VoiceWorks.Search.Contracts;
|
|
||||||
|
|
||||||
public enum VoiceWorkSortField
|
|
||||||
{
|
|
||||||
ReleaseDateNewToOld,
|
|
||||||
ReleaseDateOldToNew,
|
|
||||||
BestSelling,
|
|
||||||
MostWishedFor,
|
|
||||||
SalesToWishlistRatio,
|
|
||||||
StarRating
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
using JSMR.Application.VoiceWorks.Search.Contracts;
|
|
||||||
|
|
||||||
namespace JSMR.Application.VoiceWorks.Search;
|
|
||||||
|
|
||||||
public sealed record SearchVoiceWorksResponse(VoiceWorkSearchResults Results);
|
|
||||||
@@ -23,6 +23,8 @@ public abstract class SearchProvider<TItem, TCriteria, TSortField, TBaseQuery> :
|
|||||||
.Take(options.PageSize)
|
.Take(options.PageSize)
|
||||||
.ToArrayAsync(cancellationToken);
|
.ToArrayAsync(cancellationToken);
|
||||||
|
|
||||||
|
await PostLoadAsync(items, cancellationToken);
|
||||||
|
|
||||||
return new SearchResult<TItem>()
|
return new SearchResult<TItem>()
|
||||||
{
|
{
|
||||||
Items = items,
|
Items = items,
|
||||||
@@ -82,8 +84,7 @@ public abstract class SearchProvider<TItem, TCriteria, TSortField, TBaseQuery> :
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Expression<Func<TBaseQuery, object>> GetSortExpression(TSortField field);
|
protected abstract Expression<Func<TBaseQuery, object>> GetSortExpression(TSortField field);
|
||||||
protected abstract IOrderedQueryable<TBaseQuery> GetDefaultSortExpression(IQueryable<TBaseQuery> query);
|
|
||||||
//protected abstract (Expression<Func<TBaseQuery, object>> Selector, SortDirection Direction) GetDefaultSortExpression();
|
|
||||||
protected abstract IEnumerable<(Expression<Func<TBaseQuery, object>> Selector, SortDirection Dir)> GetDefaultSortChain();
|
protected abstract IEnumerable<(Expression<Func<TBaseQuery, object>> Selector, SortDirection Dir)> GetDefaultSortChain();
|
||||||
protected abstract IQueryable<TItem> GetSelectQuery(IOrderedQueryable<TBaseQuery> query);
|
protected abstract IQueryable<TItem> GetSelectQuery(IOrderedQueryable<TBaseQuery> query);
|
||||||
|
protected virtual Task PostLoadAsync(IList<TItem> items, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -109,9 +109,6 @@ public class CircleSearchProvider(AppDbContext context) : SearchProvider<CircleS
|
|||||||
_ => x => x.Name
|
_ => x => x.Name
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override IOrderedQueryable<CircleSearchItem> GetDefaultSortExpression(IQueryable<CircleSearchItem> query)
|
|
||||||
=> query.OrderBy(x => x.Name).ThenBy(x => x.CircleId);
|
|
||||||
|
|
||||||
protected override IEnumerable<(Expression<Func<CircleSearchItem, object>> Selector, SortDirection Dir)> GetDefaultSortChain()
|
protected override IEnumerable<(Expression<Func<CircleSearchItem, object>> Selector, SortDirection Dir)> GetDefaultSortChain()
|
||||||
{
|
{
|
||||||
yield return (x => x.Name, SortDirection.Ascending);
|
yield return (x => x.Name, SortDirection.Ascending);
|
||||||
|
|||||||
@@ -50,14 +50,9 @@ public class CreatorSearchProvider(AppDbContext context) : SearchProvider<Creato
|
|||||||
return selector;
|
return selector;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IOrderedQueryable<CreatorSearchItem> GetDefaultSortExpression(IQueryable<CreatorSearchItem> query)
|
|
||||||
{
|
|
||||||
return query.OrderBy(x => x.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IEnumerable<(Expression<Func<CreatorSearchItem, object>> Selector, SortDirection Dir)> GetDefaultSortChain()
|
protected override IEnumerable<(Expression<Func<CreatorSearchItem, object>> Selector, SortDirection Dir)> GetDefaultSortChain()
|
||||||
{
|
{
|
||||||
yield return (x => x.Name ?? string.Empty, SortDirection.Ascending);
|
yield return (x => x.Name, SortDirection.Ascending);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IOrderedQueryable<CreatorSearchItem> GetSelectQuery(IOrderedQueryable<CreatorSearchItem> query)
|
protected override IOrderedQueryable<CreatorSearchItem> GetSelectQuery(IOrderedQueryable<CreatorSearchItem> query)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using JSMR.Application.Common.Search;
|
using JSMR.Application.Common.Search;
|
||||||
using JSMR.Application.Creators.Queries.Search.Contracts;
|
|
||||||
using JSMR.Application.Tags.Queries.Search.Contracts;
|
using JSMR.Application.Tags.Queries.Search.Contracts;
|
||||||
using JSMR.Application.Tags.Queries.Search.Ports;
|
using JSMR.Application.Tags.Queries.Search.Ports;
|
||||||
using JSMR.Infrastructure.Common.Queries;
|
using JSMR.Infrastructure.Common.Queries;
|
||||||
@@ -56,14 +55,9 @@ public class TagSearchProvider(AppDbContext context) : SearchProvider<TagSearchI
|
|||||||
return selector;
|
return selector;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IOrderedQueryable<TagSearchItem> GetDefaultSortExpression(IQueryable<TagSearchItem> query)
|
|
||||||
{
|
|
||||||
return query.OrderBy(x => x.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IEnumerable<(Expression<Func<TagSearchItem, object>> Selector, SortDirection Dir)> GetDefaultSortChain()
|
protected override IEnumerable<(Expression<Func<TagSearchItem, object>> Selector, SortDirection Dir)> GetDefaultSortChain()
|
||||||
{
|
{
|
||||||
yield return (x => x.Name ?? string.Empty, SortDirection.Ascending);
|
yield return (x => x.Name, SortDirection.Ascending);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IOrderedQueryable<TagSearchItem> GetSelectQuery(IOrderedQueryable<TagSearchItem> query)
|
protected override IOrderedQueryable<TagSearchItem> GetSelectQuery(IOrderedQueryable<TagSearchItem> query)
|
||||||
|
|||||||
@@ -0,0 +1,342 @@
|
|||||||
|
using JSMR.Application.Common;
|
||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
using JSMR.Domain.Entities;
|
||||||
|
using JSMR.Infrastructure.Common.Queries;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using CircleStatus = JSMR.Application.VoiceWorks.Queries.Search.CircleStatus;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Data.Repositories.VoiceWorks;
|
||||||
|
|
||||||
|
public class VoiceWorkQuery
|
||||||
|
{
|
||||||
|
public required VoiceWork VoiceWork { get; init; }
|
||||||
|
public EnglishVoiceWork? EnglishVoiceWork { get; init; }
|
||||||
|
public required Circle Circle { get; init; }
|
||||||
|
//public VoiceWorkLocalization? VoiceWorkLocalization { get; init; }
|
||||||
|
//public VoiceWorkSearch? VoiceWorkSearch { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VoiceWorkSearchProvider(AppDbContext context) : SearchProvider<VoiceWorkSearchResult, VoiceWorkSearchCriteria, VoiceWorkSortField, VoiceWorkQuery>, IVoiceWorkSearchProvider
|
||||||
|
{
|
||||||
|
protected override IQueryable<VoiceWorkQuery> GetBaseQuery()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
from voiceWork in context.VoiceWorks
|
||||||
|
join englishVoiceWork in context.EnglishVoiceWorks on voiceWork.VoiceWorkId equals englishVoiceWork.VoiceWorkId into ps
|
||||||
|
from englishVoiceWork in ps.DefaultIfEmpty()
|
||||||
|
join circle in context.Circles on voiceWork.CircleId equals circle.CircleId into cs
|
||||||
|
from circle in cs.DefaultIfEmpty()
|
||||||
|
//join voiceWorkLocalization in context.VoiceWorkLocalizations on voiceWork.VoiceWorkId equals voiceWorkLocalization.VoiceWorkId into vwl
|
||||||
|
//from voiceWorkLocalization in vwl.DefaultIfEmpty()
|
||||||
|
//join voiceWorkSearch in context.VoiceWorkSearches on voiceWork.VoiceWorkId equals voiceWorkSearch.VoiceWorkId into vws
|
||||||
|
//from voiceWorkSearch in vws.DefaultIfEmpty()
|
||||||
|
select new VoiceWorkQuery
|
||||||
|
{
|
||||||
|
VoiceWork = voiceWork,
|
||||||
|
EnglishVoiceWork = englishVoiceWork,
|
||||||
|
Circle = circle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IQueryable<VoiceWorkQuery> ApplyFilters(IQueryable<VoiceWorkQuery> query, VoiceWorkSearchCriteria criteria)
|
||||||
|
{
|
||||||
|
IQueryable<VoiceWorkQuery> filteredQuery = query;
|
||||||
|
|
||||||
|
// TODO: Full Text Search implementation
|
||||||
|
//filteredQuery = FuzzyKeywordSearch(filteredQuery, searchProperties.Keywords);
|
||||||
|
//filteredQuery = FuzzyTitleSearch(filteredQuery, searchProperties.Title);
|
||||||
|
//filteredQuery = FuzzyCircleSearch(filteredQuery, searchProperties.Circle);
|
||||||
|
|
||||||
|
switch (criteria.SaleStatus)
|
||||||
|
{
|
||||||
|
case SaleStatus.Available:
|
||||||
|
filteredQuery = filteredQuery.Where(x => x.VoiceWork.SalesDate != null);
|
||||||
|
break;
|
||||||
|
case SaleStatus.Upcoming:
|
||||||
|
filteredQuery = filteredQuery.Where(x => x.VoiceWork.ExpectedDate != null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (criteria.ReleaseDateStart is not null)
|
||||||
|
filteredQuery = filteredQuery.Where(x => x.VoiceWork.SalesDate >= criteria.ReleaseDateStart.Value);
|
||||||
|
|
||||||
|
if (criteria.ReleaseDateEnd is not null)
|
||||||
|
filteredQuery = filteredQuery.Where(x => x.VoiceWork.SalesDate <= criteria.ReleaseDateEnd.Value);
|
||||||
|
|
||||||
|
if (criteria.AgeRatings.Length > 0)
|
||||||
|
filteredQuery = filteredQuery.Where(x => criteria.AgeRatings.Contains((AgeRating)x.VoiceWork.Rating));
|
||||||
|
|
||||||
|
if (criteria.SupportedLanguages.Length > 0)
|
||||||
|
filteredQuery = filteredQuery.Where(x => criteria.SupportedLanguages.Contains((Language)x.VoiceWork.SubtitleLanguage));
|
||||||
|
|
||||||
|
if (criteria.AIGenerationOptions.Length > 0)
|
||||||
|
filteredQuery = filteredQuery.Where(x => criteria.AIGenerationOptions.Contains((AIGeneration)x.VoiceWork.AIGeneration));
|
||||||
|
|
||||||
|
if (criteria.ShowFavoriteVoiceWorks)
|
||||||
|
filteredQuery = filteredQuery.Where(x => x.VoiceWork.Favorite);
|
||||||
|
|
||||||
|
if (criteria.ShowInvalidVoiceWorks)
|
||||||
|
filteredQuery = filteredQuery.Where(x => x.VoiceWork.IsValid != true);
|
||||||
|
|
||||||
|
if (criteria.MinDownloads is not null)
|
||||||
|
filteredQuery = filteredQuery.Where(x => x.VoiceWork.Downloads >= criteria.MinDownloads.Value);
|
||||||
|
|
||||||
|
if (criteria.MaxDownloads is not null)
|
||||||
|
filteredQuery = filteredQuery.Where(x => x.VoiceWork.Downloads <= criteria.MaxDownloads.Value);
|
||||||
|
|
||||||
|
switch (criteria.CircleStatus)
|
||||||
|
{
|
||||||
|
case CircleStatus.NotBlacklisted:
|
||||||
|
filteredQuery = filteredQuery.Where(x => x.Circle.Blacklisted == false);
|
||||||
|
break;
|
||||||
|
case CircleStatus.Favorited:
|
||||||
|
filteredQuery = filteredQuery.Where(x => x.Circle.Favorite);
|
||||||
|
break;
|
||||||
|
case CircleStatus.Blacklisted:
|
||||||
|
filteredQuery = filteredQuery.Where(x => x.Circle.Blacklisted);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredQuery = ApplyTagStatusFilter(filteredQuery, criteria);
|
||||||
|
//filteredQuery = FilterCreatorStatus(filteredQuery, searchProperties.CreatorStatus, _voiceWorkContext);
|
||||||
|
filteredQuery = FilterTagIds(filteredQuery, criteria);
|
||||||
|
filteredQuery = FilterCreatorIds(filteredQuery, criteria);
|
||||||
|
|
||||||
|
return filteredQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IQueryable<VoiceWorkQuery> ApplyTagStatusFilter(IQueryable<VoiceWorkQuery> query, VoiceWorkSearchCriteria criteria)
|
||||||
|
{
|
||||||
|
if (criteria.TagStatus is null)
|
||||||
|
return query;
|
||||||
|
|
||||||
|
// Handy local predicates that translate to EXISTS subqueries
|
||||||
|
bool HasFav(int voiceWorkId) =>
|
||||||
|
context.VoiceWorkTags
|
||||||
|
.Join(context.Tags, vwt => vwt.TagId, t => t.TagId, (vwt, t) => new { vwt, t })
|
||||||
|
.Any(x => x.vwt.VoiceWorkId == voiceWorkId && x.t.Favorite);
|
||||||
|
|
||||||
|
bool HasBlk(int voiceWorkId) =>
|
||||||
|
context.VoiceWorkTags
|
||||||
|
.Join(context.Tags, vwt => vwt.TagId, t => t.TagId, (vwt, t) => new { vwt, t })
|
||||||
|
.Any(x => x.vwt.VoiceWorkId == voiceWorkId && x.t.Blacklisted);
|
||||||
|
|
||||||
|
return criteria.TagStatus switch
|
||||||
|
{
|
||||||
|
TagStatus.NotBlacklisted =>
|
||||||
|
query.Where(q => !context.VoiceWorkTags
|
||||||
|
.Join(context.Tags, vwt => vwt.TagId, t => t.TagId, (vwt, t) => new { vwt, t })
|
||||||
|
.Any(x => x.vwt.VoiceWorkId == q.VoiceWork.VoiceWorkId && x.t.Blacklisted)),
|
||||||
|
|
||||||
|
TagStatus.Blacklisted =>
|
||||||
|
query.Where(q => context.VoiceWorkTags
|
||||||
|
.Join(context.Tags, vwt => vwt.TagId, t => t.TagId, (vwt, t) => new { vwt, t })
|
||||||
|
.Any(x => x.vwt.VoiceWorkId == q.VoiceWork.VoiceWorkId && x.t.Blacklisted)),
|
||||||
|
|
||||||
|
TagStatus.FavoriteIncludeBlacklist =>
|
||||||
|
query.Where(q => context.VoiceWorkTags
|
||||||
|
.Join(context.Tags, vwt => vwt.TagId, t => t.TagId, (vwt, t) => new { vwt, t })
|
||||||
|
.Any(x => x.vwt.VoiceWorkId == q.VoiceWork.VoiceWorkId && x.t.Favorite)),
|
||||||
|
|
||||||
|
TagStatus.FavoriteExcludeBlacklist =>
|
||||||
|
query.Where(q =>
|
||||||
|
context.VoiceWorkTags
|
||||||
|
.Join(context.Tags, vwt => vwt.TagId, t => t.TagId, (vwt, t) => new { vwt, t })
|
||||||
|
.Any(x => x.vwt.VoiceWorkId == q.VoiceWork.VoiceWorkId && x.t.Favorite)
|
||||||
|
&&
|
||||||
|
!context.VoiceWorkTags
|
||||||
|
.Join(context.Tags, vwt => vwt.TagId, t => t.TagId, (vwt, t) => new { vwt, t })
|
||||||
|
.Any(x => x.vwt.VoiceWorkId == q.VoiceWork.VoiceWorkId && x.t.Blacklisted)
|
||||||
|
),
|
||||||
|
|
||||||
|
_ => query
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IQueryable<VoiceWorkQuery> FilterTagIds(IQueryable<VoiceWorkQuery> filteredQuery, VoiceWorkSearchCriteria criteria)
|
||||||
|
{
|
||||||
|
if (criteria.TagIds.Length == 0)
|
||||||
|
return filteredQuery;
|
||||||
|
|
||||||
|
if (criteria.IncludeAllTags == false)
|
||||||
|
{
|
||||||
|
var tagQuery =
|
||||||
|
from voiceWorkTag in context.VoiceWorkTags.AsNoTracking()
|
||||||
|
where criteria.TagIds.Contains(voiceWorkTag.TagId)
|
||||||
|
select new { voiceWorkTag };
|
||||||
|
|
||||||
|
var finalTagQuery = tagQuery.Select(x => x.voiceWorkTag.VoiceWorkId);
|
||||||
|
|
||||||
|
filteredQuery = filteredQuery.Where(x => finalTagQuery.Contains(x.VoiceWork.VoiceWorkId));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (int tagId in criteria.TagIds)
|
||||||
|
{
|
||||||
|
var tagIdQuery =
|
||||||
|
from voiceWorkTag in context.VoiceWorkTags.AsNoTracking()
|
||||||
|
where voiceWorkTag.TagId == tagId
|
||||||
|
select voiceWorkTag.VoiceWorkId;
|
||||||
|
|
||||||
|
filteredQuery =
|
||||||
|
from query in filteredQuery
|
||||||
|
join voiceWorkId in tagIdQuery on query.VoiceWork.VoiceWorkId equals voiceWorkId
|
||||||
|
select new VoiceWorkQuery
|
||||||
|
{
|
||||||
|
VoiceWork = query.VoiceWork,
|
||||||
|
EnglishVoiceWork = query.EnglishVoiceWork,
|
||||||
|
Circle = query.Circle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IQueryable<VoiceWorkQuery> FilterCreatorIds(IQueryable<VoiceWorkQuery> filteredQuery, VoiceWorkSearchCriteria criteria)
|
||||||
|
{
|
||||||
|
if (criteria.CreatorIds.Length == 0)
|
||||||
|
return filteredQuery;
|
||||||
|
|
||||||
|
if (criteria.IncludeAllCreators == false)
|
||||||
|
{
|
||||||
|
var creatorQuery =
|
||||||
|
from voiceWorkCreator in context.VoiceWorkCreators.AsNoTracking()
|
||||||
|
where criteria.CreatorIds.Contains(voiceWorkCreator.CreatorId)
|
||||||
|
select new { voiceWorkCreator };
|
||||||
|
|
||||||
|
var finalCreatorQuery = creatorQuery.Select(x => x.voiceWorkCreator.VoiceWorkId);
|
||||||
|
|
||||||
|
filteredQuery = filteredQuery.Where(x => finalCreatorQuery.Contains(x.VoiceWork.VoiceWorkId));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (int creatorId in criteria.CreatorIds)
|
||||||
|
{
|
||||||
|
var creatorIdQuery =
|
||||||
|
from voiceWorkCreator in context.VoiceWorkCreators.AsNoTracking()
|
||||||
|
where voiceWorkCreator.CreatorId == creatorId
|
||||||
|
select voiceWorkCreator.VoiceWorkId;
|
||||||
|
|
||||||
|
filteredQuery =
|
||||||
|
from query in filteredQuery
|
||||||
|
join voiceWorkId in creatorIdQuery on query.VoiceWork.VoiceWorkId equals voiceWorkId
|
||||||
|
select new VoiceWorkQuery
|
||||||
|
{
|
||||||
|
VoiceWork = query.VoiceWork,
|
||||||
|
EnglishVoiceWork = query.EnglishVoiceWork,
|
||||||
|
Circle = query.Circle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<(Expression<Func<VoiceWorkQuery, object>> Selector, SortDirection Dir)> GetDefaultSortChain()
|
||||||
|
{
|
||||||
|
yield return (x => x.VoiceWork.ProductId, SortDirection.Ascending);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Expression<Func<VoiceWorkQuery, object>> GetSortExpression(VoiceWorkSortField field) => field switch
|
||||||
|
{
|
||||||
|
VoiceWorkSortField.ReleaseDate => x => x.VoiceWork.SalesDate ?? x.VoiceWork.ExpectedDate ?? DateTime.MinValue,
|
||||||
|
VoiceWorkSortField.Downloads => x => x.VoiceWork.Downloads ?? 0,
|
||||||
|
VoiceWorkSortField.WishlistCount => x => x.VoiceWork.WishlistCount ?? 0,
|
||||||
|
VoiceWorkSortField.StarRating => x => x.VoiceWork.StarRating ?? 0,
|
||||||
|
_ => x => x.VoiceWork.ProductId
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override IQueryable<VoiceWorkSearchResult> GetSelectQuery(IOrderedQueryable<VoiceWorkQuery> query)
|
||||||
|
{
|
||||||
|
var result =
|
||||||
|
from q in query
|
||||||
|
let voiceWork = q.VoiceWork
|
||||||
|
let englishVoiceWork = q.EnglishVoiceWork
|
||||||
|
let circle = q.Circle
|
||||||
|
let productLinkPage = voiceWork.SalesDate.HasValue ? "work" : "announce"
|
||||||
|
select new VoiceWorkSearchResult()
|
||||||
|
{
|
||||||
|
VoiceWorkId = voiceWork.VoiceWorkId,
|
||||||
|
ProductId = voiceWork.ProductId,
|
||||||
|
OriginalProductId = voiceWork.OriginalProductId,
|
||||||
|
ProductName = englishVoiceWork != null ? englishVoiceWork.ProductName : voiceWork.ProductName,
|
||||||
|
ProductUrl = "http://www.dlsite.com/maniax/" + productLinkPage + "/=/product_id/" + voiceWork.ProductId + ".html",
|
||||||
|
Description = englishVoiceWork != null ? englishVoiceWork.Description : voiceWork.Description,
|
||||||
|
Favorite = voiceWork.Favorite,
|
||||||
|
HasImage = voiceWork.HasImage,
|
||||||
|
Maker = circle.Name,
|
||||||
|
MakerId = circle.MakerId,
|
||||||
|
ExpectedDate = voiceWork.ExpectedDate,
|
||||||
|
SalesDate = voiceWork.SalesDate,
|
||||||
|
PlannedReleaseDate = voiceWork.PlannedReleaseDate,
|
||||||
|
Downloads = voiceWork.Downloads,
|
||||||
|
WishlistCount = voiceWork.WishlistCount,
|
||||||
|
Status = voiceWork.Status,
|
||||||
|
SubtitleLanguage = voiceWork.SubtitleLanguage,
|
||||||
|
HasTrial = voiceWork.HasTrial,
|
||||||
|
HasChobit = voiceWork.HasChobit,
|
||||||
|
IsValid = voiceWork.IsValid
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task PostLoadAsync(IList<VoiceWorkSearchResult> items, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (items.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int[] voiceWorkIds = [.. items.Select(i => i.VoiceWorkId)];
|
||||||
|
|
||||||
|
Dictionary<int, VoiceWorkTagItem[]> tagsByVw = await GetTagsAsync(voiceWorkIds, cancellationToken);
|
||||||
|
Dictionary<int, VoiceWorkCreatorItem[]> creatorsByVw = await GetCreatorsAsync(voiceWorkIds, cancellationToken);
|
||||||
|
|
||||||
|
foreach (VoiceWorkSearchResult item in items)
|
||||||
|
{
|
||||||
|
if (tagsByVw.TryGetValue(item.VoiceWorkId, out VoiceWorkTagItem[]? tags))
|
||||||
|
item.Tags = tags;
|
||||||
|
|
||||||
|
if (creatorsByVw.TryGetValue(item.VoiceWorkId, out VoiceWorkCreatorItem[]? creators))
|
||||||
|
item.Creators = creators;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Dictionary<int, VoiceWorkTagItem[]>> GetTagsAsync(int[] voiceWorkIds, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var tagRows = await (
|
||||||
|
from voiceWorkTag in context.VoiceWorkTags.AsNoTracking()
|
||||||
|
join tag in context.Tags.AsNoTracking() on voiceWorkTag.TagId equals tag.TagId
|
||||||
|
where voiceWorkIds.Contains(voiceWorkTag.VoiceWorkId)
|
||||||
|
orderby voiceWorkTag.VoiceWorkId, voiceWorkTag.Position
|
||||||
|
select new { voiceWorkTag.VoiceWorkId, voiceWorkTag.TagId, tag.Name }
|
||||||
|
).ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return tagRows
|
||||||
|
.GroupBy(r => r.VoiceWorkId)
|
||||||
|
.ToDictionary(
|
||||||
|
g => g.Key,
|
||||||
|
g => g.Select(r => new VoiceWorkTagItem { TagId = r.TagId, Name = r.Name }).ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Dictionary<int, VoiceWorkCreatorItem[]>> GetCreatorsAsync(int[] voiceWorkIds, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var creatorRows = await (
|
||||||
|
from voiceWorkCreator in context.VoiceWorkCreators.AsNoTracking()
|
||||||
|
join creator in context.Creators.AsNoTracking() on voiceWorkCreator.CreatorId equals creator.CreatorId
|
||||||
|
where voiceWorkIds.Contains(voiceWorkCreator.VoiceWorkId)
|
||||||
|
orderby voiceWorkCreator.VoiceWorkId, voiceWorkCreator.Position
|
||||||
|
select new { voiceWorkCreator.VoiceWorkId, creator.CreatorId, creator.Name }
|
||||||
|
).ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return creatorRows
|
||||||
|
.GroupBy(r => r.VoiceWorkId)
|
||||||
|
.ToDictionary(
|
||||||
|
g => g.Key,
|
||||||
|
g => g.Select(r => new VoiceWorkCreatorItem { CreatorId = r.CreatorId, Name = r.Name }).ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
65
JSMR.Tests/Fixtures/VoiceWorkSearchProviderFixture.cs
Normal file
65
JSMR.Tests/Fixtures/VoiceWorkSearchProviderFixture.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using JSMR.Application.Common;
|
||||||
|
using JSMR.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace JSMR.Tests.Fixtures;
|
||||||
|
|
||||||
|
public class VoiceWorkSearchProviderFixture : MariaDbFixture
|
||||||
|
{
|
||||||
|
protected override async Task OnInitializedAsync(AppDbContext context)
|
||||||
|
{
|
||||||
|
await SeedAsync(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task SeedAsync(AppDbContext context)
|
||||||
|
{
|
||||||
|
if (await context.VoiceWorks.AnyAsync())
|
||||||
|
return;
|
||||||
|
|
||||||
|
context.Circles.AddRange(
|
||||||
|
new() { CircleId = 1, Name = "Good Dreams", MakerId = "RG00001" },
|
||||||
|
new() { CircleId = 2, Name = "Sweet Dreams", Favorite = true, MakerId = "RG00002" },
|
||||||
|
new() { CircleId = 3, Name = "Nightmare Fuel", Blacklisted = true, MakerId = "RG00003" }
|
||||||
|
);
|
||||||
|
|
||||||
|
context.VoiceWorks.AddRange(
|
||||||
|
new() { VoiceWorkId = 1, CircleId = 1, ProductId = "RJ0000001", ProductName = "Today Sounds", Description = "An average product.", Status = (byte)VoiceWorkStatus.Available, SalesDate = new(2025, 1, 1) },
|
||||||
|
new() { VoiceWorkId = 2, CircleId = 2, ProductId = "RJ0000002", ProductName = "Super Comfy ASMR", Description = "An amazing product!", Status = (byte)VoiceWorkStatus.NewRelease, SalesDate = new(2025, 1, 3) },
|
||||||
|
new() { VoiceWorkId = 3, CircleId = 3, ProductId = "RJ0000003", ProductName = "Low Effort", Description = "A bad product.", Status = (byte)VoiceWorkStatus.Available, SalesDate = new(2025, 1, 2) },
|
||||||
|
new() { VoiceWorkId = 4, CircleId = 1, ProductId = "RJ0000004", ProductName = "Tomorrow Sounds", Description = "A average upcoming product.", Status = (byte)VoiceWorkStatus.Upcoming, ExpectedDate = new(2025, 1, 4) },
|
||||||
|
new() { VoiceWorkId = 5, CircleId = 2, ProductId = "RJ0000005", ProductName = "Super Comfy ASMR+", Description = "All your favorite sounds, plus more!", Status = (byte)VoiceWorkStatus.NewAndUpcoming, ExpectedDate = new(2025, 1, 5) }
|
||||||
|
);
|
||||||
|
|
||||||
|
context.Tags.AddRange(
|
||||||
|
new() { TagId = 1, Name = "ASMR" },
|
||||||
|
new() { TagId = 2, Name = "OL" },
|
||||||
|
new() { TagId = 3, Name = "ほのぼの" },
|
||||||
|
new() { TagId = 4, Name = "エルフ/妖精" },
|
||||||
|
new() { TagId = 5, Name = "ツンデレ", Favorite = true },
|
||||||
|
new() { TagId = 6, Name = "オールハッピー" },
|
||||||
|
new() { TagId = 7, Name = "ギャル" },
|
||||||
|
new() { TagId = 8, Name = "メイド" }
|
||||||
|
);
|
||||||
|
|
||||||
|
context.EnglishTags.AddRange(
|
||||||
|
new() { EnglishTagId = 1, TagId = 1, Name = "ASMR" },
|
||||||
|
new() { EnglishTagId = 2, TagId = 2, Name = "Office Lady" },
|
||||||
|
new() { EnglishTagId = 3, TagId = 3, Name = "Heartwarming" },
|
||||||
|
new() { EnglishTagId = 4, TagId = 4, Name = "Elf / Fairy" },
|
||||||
|
new() { EnglishTagId = 5, TagId = 5, Name = "Tsundere" },
|
||||||
|
new() { EnglishTagId = 6, TagId = 6, Name = "All Happy" },
|
||||||
|
new() { EnglishTagId = 7, TagId = 7, Name = "Gal" },
|
||||||
|
new() { EnglishTagId = 8, TagId = 8, Name = "Maid" }
|
||||||
|
);
|
||||||
|
|
||||||
|
context.Creators.AddRange(
|
||||||
|
new() { CreatorId = 1, Name = "陽向葵ゅか", Favorite = true },
|
||||||
|
new() { CreatorId = 2, Name = "秋野かえで" },
|
||||||
|
new() { CreatorId = 3, Name = "柚木つばめ" },
|
||||||
|
new() { CreatorId = 4, Name = "逢坂成美" },
|
||||||
|
new() { CreatorId = 5, Name = "山田じぇみ子", Blacklisted = true }
|
||||||
|
);
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
92
JSMR.Tests/Integration/VoiceWorkSearchProviderTests.cs
Normal file
92
JSMR.Tests/Integration/VoiceWorkSearchProviderTests.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using JSMR.Application.Common.Search;
|
||||||
|
using JSMR.Application.VoiceWorks.Queries.Search;
|
||||||
|
using JSMR.Infrastructure.Data;
|
||||||
|
using JSMR.Infrastructure.Data.Repositories.VoiceWorks;
|
||||||
|
using JSMR.Tests.Fixtures;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace JSMR.Tests.Integration;
|
||||||
|
|
||||||
|
public class VoiceWorkSearchProviderTests(VoiceWorkSearchProviderFixture fixture) : IClassFixture<VoiceWorkSearchProviderFixture>
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Filter_Default()
|
||||||
|
{
|
||||||
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
|
VoiceWorkSearchProvider provider = new(context);
|
||||||
|
|
||||||
|
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
||||||
|
{
|
||||||
|
Criteria = new()
|
||||||
|
{
|
||||||
|
SaleStatus = SaleStatus.Available,
|
||||||
|
CircleStatus = CircleStatus.NotBlacklisted
|
||||||
|
},
|
||||||
|
SortOptions =
|
||||||
|
[
|
||||||
|
new(VoiceWorkSortField.ReleaseDate, Application.Common.Search.SortDirection.Descending)
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await provider.SearchAsync(options);
|
||||||
|
|
||||||
|
result.Items.Length.ShouldBe(2);
|
||||||
|
result.TotalItems.ShouldBe(2);
|
||||||
|
result.Items.ShouldAllBe(item => item.SalesDate != null);
|
||||||
|
result.Items.ShouldNotContain(item => item.ExpectedDate != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Filter_Upcoming_Favorite()
|
||||||
|
{
|
||||||
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
|
VoiceWorkSearchProvider provider = new(context);
|
||||||
|
|
||||||
|
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
||||||
|
{
|
||||||
|
Criteria = new()
|
||||||
|
{
|
||||||
|
SaleStatus = SaleStatus.Upcoming,
|
||||||
|
CircleStatus = CircleStatus.Favorited
|
||||||
|
},
|
||||||
|
SortOptions =
|
||||||
|
[
|
||||||
|
new(VoiceWorkSortField.ReleaseDate, Application.Common.Search.SortDirection.Descending)
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await provider.SearchAsync(options);
|
||||||
|
|
||||||
|
result.Items.Length.ShouldBe(1);
|
||||||
|
result.TotalItems.ShouldBe(1);
|
||||||
|
result.Items.ShouldAllBe(item => item.ExpectedDate != null);
|
||||||
|
result.Items.ShouldNotContain(item => item.SalesDate != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Filter_Availble_Blacklisted()
|
||||||
|
{
|
||||||
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
|
VoiceWorkSearchProvider provider = new(context);
|
||||||
|
|
||||||
|
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
||||||
|
{
|
||||||
|
Criteria = new()
|
||||||
|
{
|
||||||
|
SaleStatus = SaleStatus.Available,
|
||||||
|
CircleStatus = CircleStatus.Blacklisted
|
||||||
|
},
|
||||||
|
SortOptions =
|
||||||
|
[
|
||||||
|
new(VoiceWorkSortField.ReleaseDate, Application.Common.Search.SortDirection.Descending)
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await provider.SearchAsync(options);
|
||||||
|
|
||||||
|
result.Items.Length.ShouldBe(1);
|
||||||
|
result.TotalItems.ShouldBe(1);
|
||||||
|
result.Items.ShouldAllBe(item => item.SalesDate != null);
|
||||||
|
result.Items.ShouldNotContain(item => item.ExpectedDate != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user