@@ -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 ( )
) ;
}
}