feat(176): search field catalog query + curated list
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
using ErsatzTV.Core.Api.Search;
|
||||
|
||||
namespace ErsatzTV.Application.Search.Queries;
|
||||
|
||||
public record GetSearchFieldCatalog : IRequest<List<SearchFieldResponseModel>>;
|
||||
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Core.Api.Search;
|
||||
|
||||
namespace ErsatzTV.Application.Search.Queries;
|
||||
|
||||
public class GetSearchFieldCatalogHandler : IRequestHandler<GetSearchFieldCatalog, List<SearchFieldResponseModel>>
|
||||
{
|
||||
public Task<List<SearchFieldResponseModel>> Handle(
|
||||
GetSearchFieldCatalog request,
|
||||
CancellationToken cancellationToken) =>
|
||||
Task.FromResult(SearchFieldCatalog.Fields);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using ErsatzTV.Core.Api.Search;
|
||||
|
||||
namespace ErsatzTV.Application.Search;
|
||||
|
||||
public static class SearchFieldCatalog
|
||||
{
|
||||
private static readonly string[] None = [];
|
||||
|
||||
// Allowed values for the `type` enum — mirrors the lowercase tokens the Lucene index stores for
|
||||
// the `type` field (see LuceneSearchIndex.TypeField and its *Type constants).
|
||||
private static readonly string[] ItemTypes =
|
||||
[
|
||||
"movie", "show", "season", "episode", "artist", "music_video", "other_video", "song", "image",
|
||||
"remote_stream"
|
||||
];
|
||||
|
||||
public static readonly List<SearchFieldResponseModel> Fields =
|
||||
[
|
||||
// General
|
||||
new("title", "Title", "text", "General", None),
|
||||
new("genre", "Genre", "text", "General", None),
|
||||
new("tag", "Tag", "text", "General", None),
|
||||
new("plot", "Plot", "fulltext", "General", None),
|
||||
new("content_rating", "Content rating", "text", "General", None),
|
||||
new("studio", "Studio", "text", "General", None),
|
||||
new("collection", "Collection", "text", "General", None),
|
||||
new("state", "State", "text", "General", None),
|
||||
new("type", "Item type", "enum", "General", ItemTypes),
|
||||
|
||||
// TV
|
||||
new("network", "Network", "text", "TV", None),
|
||||
new("show_title", "Show title", "text", "TV", None),
|
||||
new("show_genre", "Show genre", "text", "TV", None),
|
||||
new("season_number", "Season number", "number", "TV", None),
|
||||
new("episode_number", "Episode number", "number", "TV", None),
|
||||
|
||||
// Movie / People
|
||||
new("director", "Director", "text", "Movie", None),
|
||||
new("writer", "Writer", "text", "Movie", None),
|
||||
new("actor", "Actor", "text", "Movie", None),
|
||||
|
||||
// Music
|
||||
new("artist", "Artist", "text", "Music", None),
|
||||
new("album", "Album", "text", "Music", None),
|
||||
new("album_artist", "Album artist", "text", "Music", None),
|
||||
|
||||
// Technical
|
||||
new("minutes", "Duration (min)", "number", "Technical", None),
|
||||
new("height", "Height (px)", "number", "Technical", None),
|
||||
new("width", "Width (px)", "number", "Technical", None),
|
||||
new("video_codec", "Video codec", "text", "Technical", None),
|
||||
new("video_dynamic_range", "Dynamic range", "text", "Technical", None),
|
||||
|
||||
// Dates
|
||||
new("added_date", "Date added", "date", "Dates", None),
|
||||
new("release_date", "Release date", "date", "Dates", None)
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace ErsatzTV.Core.Api.Search;
|
||||
|
||||
/// <summary>
|
||||
/// One filterable field in the visual rule builder's catalog.
|
||||
/// <c>Type</c> is one of: text, fulltext, number, date, enum.
|
||||
/// <c>Values</c> is populated only for <c>enum</c> fields (allowed dropdown values); empty otherwise.
|
||||
/// </summary>
|
||||
public record SearchFieldResponseModel(string Name, string Label, string Type, string Group, string[] Values);
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Application.Search.Queries;
|
||||
using ErsatzTV.Core.Api.Search;
|
||||
using NUnit.Framework;
|
||||
using Shouldly;
|
||||
|
||||
namespace ErsatzTV.Tests.Application.Search;
|
||||
|
||||
[TestFixture]
|
||||
public class GetSearchFieldCatalogHandlerTests
|
||||
{
|
||||
[Test]
|
||||
public async Task Returns_Curated_Catalog_Without_Internal_Fields()
|
||||
{
|
||||
var handler = new GetSearchFieldCatalogHandler();
|
||||
|
||||
List<SearchFieldResponseModel> result = await handler.Handle(new GetSearchFieldCatalog(), CancellationToken.None);
|
||||
|
||||
result.ShouldNotBeEmpty();
|
||||
result.Select(f => f.Name).ShouldNotContain("id");
|
||||
result.Select(f => f.Name).ShouldNotContain("tag_full");
|
||||
result.Select(f => f.Name).ShouldNotContain("library_folder_id");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Every_Field_Has_A_Known_Type_And_A_Group()
|
||||
{
|
||||
var handler = new GetSearchFieldCatalogHandler();
|
||||
string[] knownTypes = ["text", "fulltext", "number", "date", "enum"];
|
||||
|
||||
List<SearchFieldResponseModel> result = await handler.Handle(new GetSearchFieldCatalog(), CancellationToken.None);
|
||||
|
||||
foreach (SearchFieldResponseModel field in result)
|
||||
{
|
||||
knownTypes.ShouldContain(field.Type);
|
||||
field.Group.ShouldNotBeNullOrWhiteSpace();
|
||||
field.Label.ShouldNotBeNullOrWhiteSpace();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Enum_Fields_Carry_NonEmpty_Values_And_NonEnum_Do_Not()
|
||||
{
|
||||
var handler = new GetSearchFieldCatalogHandler();
|
||||
|
||||
List<SearchFieldResponseModel> result = await handler.Handle(new GetSearchFieldCatalog(), CancellationToken.None);
|
||||
|
||||
foreach (SearchFieldResponseModel field in result)
|
||||
{
|
||||
if (field.Type == "enum")
|
||||
{
|
||||
field.Values.ShouldNotBeEmpty();
|
||||
}
|
||||
else
|
||||
{
|
||||
field.Values.ShouldBeEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user