Files
ersatztv/ErsatzTV.Application/Search/Queries/GetSearchFieldValuesHandler.cs
T
timothyandClaude Opus 4.8 b4ae0bde18 feat(434): distinct-values search endpoint (text fields, Lucene term enumeration)
Adds GET /api/v1/search/fields/{name}/values?q=&limit= — the backend slice of the
visual rule builder's facet-value typeahead (#434). Enumerates distinct Lucene term
values for a text field via MultiFields.GetTerms + TermsEnum, filtered by a
case-insensitive prefix, limit clamped to [1,50]. 404s when the field is absent from
SearchFieldCatalog or is not type "text". ElasticSearchIndex (the optional external
backend) throws NotSupportedException for this method — its text fields are analyzed,
not keyword-mapped, so a terms aggregation isn't safe to guess at without verifying
against a live cluster.

Regenerated OpenAPI trio (v1.json, v1.d.ts, endpoint-index.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 20:43:27 +02:00

29 lines
1.0 KiB
C#

using ErsatzTV.Core.Api.Search;
using ErsatzTV.Core.Interfaces.Search;
namespace ErsatzTV.Application.Search.Queries;
public class GetSearchFieldValuesHandler(ISearchIndex searchIndex)
: IRequestHandler<GetSearchFieldValues, Option<SearchFieldValuesResponseModel>>
{
private const int DefaultLimit = 50;
private const int MaxLimit = 50;
public async Task<Option<SearchFieldValuesResponseModel>> Handle(
GetSearchFieldValues request,
CancellationToken cancellationToken)
{
SearchFieldResponseModel field = SearchFieldCatalog.Fields
.FirstOrDefault(f => f.Name == request.Name);
if (field is null || field.Type != "text")
{
return Option<SearchFieldValuesResponseModel>.None;
}
int limit = request.Limit <= 0 ? DefaultLimit : Math.Clamp(request.Limit, 1, MaxLimit);
List<string> values = await searchIndex.GetFieldValues(request.Name, request.Query, limit);
return new SearchFieldValuesResponseModel(values);
}
}