Added full-text search to voice works search provider. Added initial tests for voice works full-text search.

This commit is contained in:
2025-08-31 23:29:01 -04:00
parent 3d0b2ed31d
commit a73a2ea1c9
8 changed files with 214 additions and 43 deletions

View File

@@ -20,8 +20,9 @@ public sealed class VoiceWorkSearchConfiguration : IEntityTypeConfiguration<Voic
// MariaDB/MySQL (Pomelo) fulltext (BOOLEAN/NATURAL) — create an FT index
// Pomelo supports .HasMethod("FULLTEXT"). If your version doesn't, add it in a migration SQL.
//builder.HasIndex(x => x.SearchText)
// .HasDatabaseName("FT_SearchText")
// .HasMethod("FULLTEXT");
builder.HasIndex(x => x.SearchText)
.IsFullText()
.HasDatabaseName("FT_SearchText");
//.HasMethod("FULLTEXT");
}
}

View File

@@ -0,0 +1,6 @@
namespace JSMR.Infrastructure.Data.Repositories.VoiceWorks;
public interface IVoiceWorkFullTextSearch
{
IQueryable<int> MatchingIds(AppDbContext context, string searchText);
}

View File

@@ -0,0 +1,11 @@
using Microsoft.EntityFrameworkCore;
namespace JSMR.Infrastructure.Data.Repositories.VoiceWorks;
public class MySqlVoiceWorkFullTextSearch : IVoiceWorkFullTextSearch
{
public IQueryable<int> MatchingIds(AppDbContext context, string searchText) =>
context.VoiceWorkSearches
.Where(v => EF.Functions.Match(v.SearchText, searchText, MySqlMatchSearchMode.Boolean) > 0)
.Select(v => v.VoiceWorkId);
}

View File

@@ -18,15 +18,15 @@ public class VoiceWorkQuery
//public VoiceWorkSearch? VoiceWorkSearch { get; init; }
}
public class VoiceWorkSearchProvider(AppDbContext context) : SearchProvider<VoiceWorkSearchResult, VoiceWorkSearchCriteria, VoiceWorkSortField, VoiceWorkQuery>, IVoiceWorkSearchProvider
public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSearch fullTextSearch) : 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 voiceWork in context.VoiceWorks.AsNoTracking()
join englishVoiceWork in context.EnglishVoiceWorks.AsNoTracking() 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
join circle in context.Circles.AsNoTracking() 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()
@@ -44,10 +44,12 @@ public class VoiceWorkSearchProvider(AppDbContext context) : SearchProvider<Voic
{
IQueryable<VoiceWorkQuery> filteredQuery = query;
// TODO: Full Text Search implementation
//filteredQuery = FuzzyKeywordSearch(filteredQuery, searchProperties.Keywords);
//filteredQuery = FuzzyTitleSearch(filteredQuery, searchProperties.Title);
//filteredQuery = FuzzyCircleSearch(filteredQuery, searchProperties.Circle);
filteredQuery = ApplyKeywordsFilter(filteredQuery, criteria);
filteredQuery = ApplyCircleStatusFilter(filteredQuery, criteria);
filteredQuery = ApplyTagStatusFilter(filteredQuery, criteria);
//filteredQuery = FilterCreatorStatus(filteredQuery, searchProperties.CreatorStatus, _voiceWorkContext);
filteredQuery = ApplyTagIdsFilter(filteredQuery, criteria);
filteredQuery = ApplyCreatorIdsFilter(filteredQuery, criteria);
switch (criteria.SaleStatus)
{
@@ -86,25 +88,43 @@ public class VoiceWorkSearchProvider(AppDbContext context) : SearchProvider<Voic
if (criteria.MaxDownloads is not null)
filteredQuery = filteredQuery.Where(x => x.VoiceWork.Downloads <= criteria.MaxDownloads.Value);
return filteredQuery;
}
private IQueryable<VoiceWorkQuery> ApplyKeywordsFilter(IQueryable<VoiceWorkQuery> query, VoiceWorkSearchCriteria criteria)
{
if (string.IsNullOrWhiteSpace(criteria.Keywords))
return query;
var voiceWorkIds = fullTextSearch.MatchingIds(context, criteria.Keywords);
return query.Where(x => voiceWorkIds.Contains(x.VoiceWork.VoiceWorkId));
}
private IQueryable<VoiceWorkQuery> ApplyCircleStatusFilter(IQueryable<VoiceWorkQuery> query, VoiceWorkSearchCriteria criteria)
{
if (criteria.CircleStatus is null)
return query;
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);
query = query.Where(q =>
!context.Circles.Any(c => c.CircleId == q.VoiceWork.CircleId && c.Blacklisted));
break;
case CircleStatus.Blacklisted:
filteredQuery = filteredQuery.Where(x => x.Circle.Blacklisted);
query = query.Where(q =>
context.Circles.Any(c => c.CircleId == q.VoiceWork.CircleId && c.Blacklisted));
break;
case CircleStatus.Favorited:
query = query.Where(q =>
context.Circles.Any(c => c.CircleId == q.VoiceWork.CircleId && c.Favorite));
break;
}
filteredQuery = ApplyTagStatusFilter(filteredQuery, criteria);
//filteredQuery = FilterCreatorStatus(filteredQuery, searchProperties.CreatorStatus, _voiceWorkContext);
filteredQuery = FilterTagIds(filteredQuery, criteria);
filteredQuery = FilterCreatorIds(filteredQuery, criteria);
return filteredQuery;
return query;
}
private IQueryable<VoiceWorkQuery> ApplyTagStatusFilter(IQueryable<VoiceWorkQuery> query, VoiceWorkSearchCriteria criteria)
@@ -155,7 +175,7 @@ public class VoiceWorkSearchProvider(AppDbContext context) : SearchProvider<Voic
};
}
private IQueryable<VoiceWorkQuery> FilterTagIds(IQueryable<VoiceWorkQuery> filteredQuery, VoiceWorkSearchCriteria criteria)
private IQueryable<VoiceWorkQuery> ApplyTagIdsFilter(IQueryable<VoiceWorkQuery> filteredQuery, VoiceWorkSearchCriteria criteria)
{
if (criteria.TagIds.Length == 0)
return filteredQuery;
@@ -195,7 +215,7 @@ public class VoiceWorkSearchProvider(AppDbContext context) : SearchProvider<Voic
return filteredQuery;
}
private IQueryable<VoiceWorkQuery> FilterCreatorIds(IQueryable<VoiceWorkQuery> filteredQuery, VoiceWorkSearchCriteria criteria)
private IQueryable<VoiceWorkQuery> ApplyCreatorIdsFilter(IQueryable<VoiceWorkQuery> filteredQuery, VoiceWorkSearchCriteria criteria)
{
if (criteria.CreatorIds.Length == 0)
return filteredQuery;