Skip to content

Commit 3cadd35

Browse files
Start-item support in RTE markup (#14283)
* Append start item to RTE markup * Split the Examine specifics from API query service (#14257) * Split the Examine specifics from API query service to a provider based model * Review changes: Use paged model as provider return value + add logging * Fix version * Revert incidentally changed version number by targeting wrong branch * Fix wrongly changed version number --- it's RC2 now --------- Co-authored-by: Nikolaj <[email protected]>
1 parent 7776cde commit 3cadd35

File tree

14 files changed

+586
-314
lines changed

14 files changed

+586
-314
lines changed

src/Umbraco.Cms.Api.Delivery/Controllers/ContentApiControllerBase.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ protected IActionResult ApiContentQueryOperationStatusResult(ApiContentQueryOper
3131
.WithTitle("Filter option not found")
3232
.WithDetail("One of the attempted 'filter' options does not exist")
3333
.Build()),
34-
ApiContentQueryOperationStatus.IndexNotFound => BadRequest(new ProblemDetailsBuilder()
35-
.WithTitle("Examine index not found")
36-
.WithDetail($"No index found with name {Constants.UmbracoIndexes.DeliveryApiContentIndexName}")
37-
.Build()),
3834
ApiContentQueryOperationStatus.SelectorOptionNotFound => BadRequest(new ProblemDetailsBuilder()
3935
.WithTitle("Selector option not found")
4036
.WithDetail("The attempted 'fetch' option does not exist")

src/Umbraco.Cms.Api.Delivery/DependencyInjection/UmbracoBuilderExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public static IUmbracoBuilder AddDeliveryApi(this IUmbracoBuilder builder)
2828
builder.Services.AddSingleton<IRequestStartItemProviderAccessor, RequestContextRequestStartItemProviderAccessor>();
2929
builder.Services.AddSingleton<IApiAccessService, ApiAccessService>();
3030
builder.Services.AddSingleton<IApiContentQueryService, ApiContentQueryService>();
31+
builder.Services.AddSingleton<IApiContentQueryProvider, ApiContentQueryProvider>();
3132

3233
builder.Services.ConfigureOptions<ConfigureUmbracoDeliveryApiSwaggerGenOptions>();
3334
builder.AddUmbracoApiOpenApiUI();
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
using Examine;
2+
using Examine.Search;
3+
using Microsoft.Extensions.Logging;
4+
using Umbraco.Cms.Core;
5+
using Umbraco.Cms.Core.DeliveryApi;
6+
using Umbraco.Cms.Infrastructure.Examine;
7+
using Umbraco.Extensions;
8+
using Umbraco.New.Cms.Core.Models;
9+
10+
namespace Umbraco.Cms.Api.Delivery.Services;
11+
12+
/// <summary>
13+
/// This is the Examine implementation of content querying for the Delivery API.
14+
/// </summary>
15+
internal sealed class ApiContentQueryProvider : IApiContentQueryProvider
16+
{
17+
private const string ItemIdFieldName = "itemId";
18+
private readonly IExamineManager _examineManager;
19+
private readonly ILogger<ApiContentQueryProvider> _logger;
20+
private readonly string _fallbackGuidValue;
21+
private readonly Dictionary<string, FieldType> _fieldTypes;
22+
23+
public ApiContentQueryProvider(
24+
IExamineManager examineManager,
25+
ContentIndexHandlerCollection indexHandlers,
26+
ILogger<ApiContentQueryProvider> logger)
27+
{
28+
_examineManager = examineManager;
29+
_logger = logger;
30+
31+
// A fallback value is needed for Examine queries in case we don't have a value - we can't pass null or empty string
32+
// It is set to a random guid since this would be highly unlikely to yield any results
33+
_fallbackGuidValue = Guid.NewGuid().ToString("D");
34+
35+
// build a look-up dictionary of field types by field name
36+
_fieldTypes = indexHandlers
37+
.SelectMany(handler => handler.GetFields())
38+
.DistinctBy(field => field.FieldName)
39+
.ToDictionary(field => field.FieldName, field => field.FieldType, StringComparer.InvariantCultureIgnoreCase);
40+
}
41+
42+
public PagedModel<Guid> ExecuteQuery(SelectorOption selectorOption, IList<FilterOption> filterOptions, IList<SortOption> sortOptions, string culture, int skip, int take)
43+
{
44+
if (!_examineManager.TryGetIndex(Constants.UmbracoIndexes.DeliveryApiContentIndexName, out IIndex? index))
45+
{
46+
_logger.LogError("Could not find the index {IndexName} when attempting to execute a query.", Constants.UmbracoIndexes.DeliveryApiContentIndexName);
47+
return new PagedModel<Guid>();
48+
}
49+
50+
IBooleanOperation queryOperation = BuildSelectorOperation(selectorOption, index, culture);
51+
52+
ApplyFiltering(filterOptions, queryOperation);
53+
ApplySorting(sortOptions, queryOperation);
54+
55+
ISearchResults? results = queryOperation
56+
.SelectField(ItemIdFieldName)
57+
.Execute(QueryOptions.SkipTake(skip, take));
58+
59+
if (results is null)
60+
{
61+
// The query yield no results
62+
return new PagedModel<Guid>();
63+
}
64+
65+
Guid[] items = results
66+
.Where(r => r.Values.ContainsKey(ItemIdFieldName))
67+
.Select(r => Guid.Parse(r.Values[ItemIdFieldName]))
68+
.ToArray();
69+
70+
return new PagedModel<Guid>(results.TotalItemCount, items);
71+
}
72+
73+
public SelectorOption AllContentSelectorOption() => new()
74+
{
75+
FieldName = UmbracoExamineFieldNames.CategoryFieldName, Values = new[] { "content" }
76+
};
77+
78+
private IBooleanOperation BuildSelectorOperation(SelectorOption selectorOption, IIndex index, string culture)
79+
{
80+
IQuery query = index.Searcher.CreateQuery();
81+
82+
IBooleanOperation selectorOperation = selectorOption.Values.Length == 1
83+
? query.Field(selectorOption.FieldName, selectorOption.Values.First())
84+
: query.GroupedOr(new[] { selectorOption.FieldName }, selectorOption.Values);
85+
86+
// Item culture must be either the requested culture or "none"
87+
selectorOperation.And().GroupedOr(new[] { UmbracoExamineFieldNames.DeliveryApiContentIndex.Culture }, culture.ToLowerInvariant().IfNullOrWhiteSpace(_fallbackGuidValue), "none");
88+
89+
return selectorOperation;
90+
}
91+
92+
private void ApplyFiltering(IList<FilterOption> filterOptions, IBooleanOperation queryOperation)
93+
{
94+
void HandleExact(IQuery query, string fieldName, string[] values)
95+
{
96+
if (values.Length == 1)
97+
{
98+
query.Field(fieldName, values[0]);
99+
}
100+
else
101+
{
102+
query.GroupedOr(new[] { fieldName }, values);
103+
}
104+
}
105+
106+
foreach (FilterOption filterOption in filterOptions)
107+
{
108+
var values = filterOption.Values.Any()
109+
? filterOption.Values
110+
: new[] { _fallbackGuidValue };
111+
112+
switch (filterOption.Operator)
113+
{
114+
case FilterOperation.Is:
115+
// TODO: test this for explicit word matching
116+
HandleExact(queryOperation.And(), filterOption.FieldName, values);
117+
break;
118+
case FilterOperation.IsNot:
119+
// TODO: test this for explicit word matching
120+
HandleExact(queryOperation.Not(), filterOption.FieldName, values);
121+
break;
122+
// TODO: Fix
123+
case FilterOperation.Contains:
124+
break;
125+
// TODO: Fix
126+
case FilterOperation.DoesNotContain:
127+
break;
128+
default:
129+
continue;
130+
}
131+
}
132+
}
133+
134+
private void ApplySorting(IList<SortOption> sortOptions, IOrdering ordering)
135+
{
136+
foreach (SortOption sort in sortOptions)
137+
{
138+
if (_fieldTypes.TryGetValue(sort.FieldName, out FieldType fieldType) is false)
139+
{
140+
_logger.LogWarning(
141+
"Sort implementation for field name {FieldName} does not match an index handler implementation, cannot resolve field type.",
142+
sort.FieldName);
143+
continue;
144+
}
145+
146+
SortType sortType = fieldType switch
147+
{
148+
FieldType.Number => SortType.Int,
149+
FieldType.Date => SortType.Long,
150+
FieldType.StringRaw => SortType.String,
151+
FieldType.StringAnalyzed => SortType.String,
152+
FieldType.StringSortable => SortType.String,
153+
_ => throw new ArgumentOutOfRangeException(nameof(fieldType))
154+
};
155+
156+
ordering = sort.Direction switch
157+
{
158+
Direction.Ascending => ordering.OrderBy(new SortableField(sort.FieldName, sortType)),
159+
Direction.Descending => ordering.OrderByDescending(new SortableField(sort.FieldName, sortType)),
160+
_ => ordering
161+
};
162+
}
163+
}
164+
}

0 commit comments

Comments
 (0)