Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/i18n/frontend_de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,9 @@
"schemas.schemaNameHint": "Sie können nur Buchstaben, Zahlen und Bindestriche verwenden und nicht mehr als 40 Zeichen.",
"schemas.schemaNameValidationMessage": "Der Name darf nur Buchstaben, Zahlen und Bindestriche enthalten.",
"schemas.schemaTagsHint": "Tags zur Annotation Ihres Schemas für Automatisierungsprozesse.",
"schemas.searchFields": "Search Fields",
"schemas.searchFieldsHelp": "The normal search uses **full-text search** across the available data.\n\nYou can optionally define **1–3 additional search fields**. These fields are included in the search using a simple **“contains” match**, meaning results are returned when the entered text appears anywhere within those fields.",
"schemas.searchFieldsHint": "The search fields",
"schemas.searchPlaceholder": "Suchen",
"schemas.showFieldFailed": "Fehler beim Anzeigen des Feldes. Bitte neu laden.",
"schemas.synchronized": "Schema erfolgreich synchronisiert.",
Expand Down
3 changes: 3 additions & 0 deletions backend/i18n/frontend_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,9 @@
"schemas.schemaNameHint": "You can only use letters, numbers and dashes and not more than 40 characters.",
"schemas.schemaNameValidationMessage": "Name can only contain letters, numbers and dashes.",
"schemas.schemaTagsHint": "Tags to annotate your schema for automation processes.",
"schemas.searchFields": "Search Fields",
"schemas.searchFieldsHelp": "The normal search uses **full-text search** across the available data.\n\nYou can optionally define **1–3 additional search fields**. These fields are included in the search using a simple **“contains” match**, meaning results are returned when the entered text appears anywhere within those fields.",
"schemas.searchFieldsHint": "The search fields",
"schemas.searchPlaceholder": "Search",
"schemas.showFieldFailed": "Failed to show field. Please reload.",
"schemas.synchronized": "Schema synchronized successfully.",
Expand Down
3 changes: 3 additions & 0 deletions backend/i18n/frontend_fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,9 @@
"schemas.schemaNameHint": "Vous ne pouvez utiliser que des lettres, des chiffres et des tirets et pas plus de 40 caractères.",
"schemas.schemaNameValidationMessage": "Le nom ne peut contenir que des lettres, des chiffres et des tirets.",
"schemas.schemaTagsHint": "Balises pour annoter votre schéma pour les processus d'automatisation.",
"schemas.searchFields": "Search Fields",
"schemas.searchFieldsHelp": "The normal search uses **full-text search** across the available data.\n\nYou can optionally define **1–3 additional search fields**. These fields are included in the search using a simple **“contains” match**, meaning results are returned when the entered text appears anywhere within those fields.",
"schemas.searchFieldsHint": "The search fields",
"schemas.searchPlaceholder": "Recherche",
"schemas.showFieldFailed": "Impossible d'afficher le champ. Veuillez recharger.",
"schemas.synchronized": "Schéma synchronisé avec succès.",
Expand Down
3 changes: 3 additions & 0 deletions backend/i18n/frontend_it.json
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,9 @@
"schemas.schemaNameHint": "Puoi utilizzare solo lettere, numeri e trattini e un numero massimo di 40 caratteri.",
"schemas.schemaNameValidationMessage": "Il nome può contenere solo lettere, numeri e trattini.",
"schemas.schemaTagsHint": "Tag per descrivere il tuo schema per i processi automatici.",
"schemas.searchFields": "Search Fields",
"schemas.searchFieldsHelp": "The normal search uses **full-text search** across the available data.\n\nYou can optionally define **1–3 additional search fields**. These fields are included in the search using a simple **“contains” match**, meaning results are returned when the entered text appears anywhere within those fields.",
"schemas.searchFieldsHint": "The search fields",
"schemas.searchPlaceholder": "Cerca negli schemi...",
"schemas.showFieldFailed": "Non è stato possibile mostrare il campo. Per favore ricarica.",
"schemas.synchronized": "Lo Schema è stato sincronizzato con successo.",
Expand Down
3 changes: 3 additions & 0 deletions backend/i18n/frontend_nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,9 @@
"schemas.schemaNameHint": "Je mag alleen letters, cijfers en streepjes gebruiken en niet meer dan 40 tekens.",
"schemas.schemaNameValidationMessage": "Naam mag alleen letters, cijfers en streepjes bevatten.",
"schemas.schemaTagsHint": "Tags om uw schema voor automatiseringsprocessen te annoteren.",
"schemas.searchFields": "Search Fields",
"schemas.searchFieldsHelp": "The normal search uses **full-text search** across the available data.\n\nYou can optionally define **1–3 additional search fields**. These fields are included in the search using a simple **“contains” match**, meaning results are returned when the entered text appears anywhere within those fields.",
"schemas.searchFieldsHint": "The search fields",
"schemas.searchPlaceholder": "Zoek schema's ...",
"schemas.showFieldFailed": "Kan veld niet weergeven. Laad opnieuw.",
"schemas.synchronized": "Schema is succesvol gesynchroniseerd.",
Expand Down
3 changes: 3 additions & 0 deletions backend/i18n/frontend_pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,9 @@
"schemas.schemaNameHint": "Só pode utilizar letras, números e traços e não mais de 40 caracteres.",
"schemas.schemaNameValidationMessage": "O nome só pode conter letras, números e traços.",
"schemas.schemaTagsHint": "Etiquetas para anotar o seu esquema para processos de automação.",
"schemas.searchFields": "Search Fields",
"schemas.searchFieldsHelp": "The normal search uses **full-text search** across the available data.\n\nYou can optionally define **1–3 additional search fields**. These fields are included in the search using a simple **“contains” match**, meaning results are returned when the entered text appears anywhere within those fields.",
"schemas.searchFieldsHint": "The search fields",
"schemas.searchPlaceholder": "Pesquisar",
"schemas.showFieldFailed": "Falhou em mostrar o campo. Por favor, recarregue.",
"schemas.synchronized": "Esquema sincronizado com sucesso.",
Expand Down
3 changes: 3 additions & 0 deletions backend/i18n/frontend_zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,9 @@
"schemas.schemaNameHint": "您只能使用字母、数字和破折号,并且不能超过 40 个字符。",
"schemas.schemaNameValidationMessage": "名称只能包含字母、数字和破折号。",
"schemas.schemaTagsHint": "用于注释自动化流程Schemas的标签。",
"schemas.searchFields": "Search Fields",
"schemas.searchFieldsHelp": "The normal search uses **full-text search** across the available data.\n\nYou can optionally define **1–3 additional search fields**. These fields are included in the search using a simple **“contains” match**, meaning results are returned when the entered text appears anywhere within those fields.",
"schemas.searchFieldsHint": "The search fields",
"schemas.searchPlaceholder": "搜索Schemas...",
"schemas.showFieldFailed": "显示字段失败。请重新加载。",
"schemas.synchronized": "Schema synchronized successfully.",
Expand Down
3 changes: 3 additions & 0 deletions backend/i18n/source/frontend_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,9 @@
"schemas.schemaNameHint": "You can only use letters, numbers and dashes and not more than 40 characters.",
"schemas.schemaNameValidationMessage": "Name can only contain letters, numbers and dashes.",
"schemas.schemaTagsHint": "Tags to annotate your schema for automation processes.",
"schemas.searchFields": "Search Fields",
"schemas.searchFieldsHelp": "The normal search uses **full-text search** across the available data.\n\nYou can optionally define **1–3 additional search fields**. These fields are included in the search using a simple **“contains” match**, meaning results are returned when the entered text appears anywhere within those fields.",
"schemas.searchFieldsHint": "The search fields",
"schemas.searchPlaceholder": "Search",
"schemas.showFieldFailed": "Failed to show field. Please reload.",
"schemas.synchronized": "Schema synchronized successfully.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public sealed record SchemaProperties : NamedElementPropertiesBase

public ReadonlyList<string>? Tags { get; init; }

public FieldNames? SearchFields { get; init; }

public string? ContentsSidebarUrl { get; init; }

public string? ContentSidebarUrl { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class ContentQueryParser(
private static readonly TimeSpan CacheTime = TimeSpan.FromMinutes(60);
private readonly ContentsOptions options = options.Value;

public virtual async Task<Q> ParseAsync(Context context, Q q, Schema? schema = null,
public virtual async Task<Q?> ParseAsync(Context context, Q q, Schema? schema = null,
CancellationToken ct = default)
{
Guard.NotNull(context);
Expand All @@ -44,7 +44,11 @@ public virtual async Task<Q> ParseAsync(Context context, Q q, Schema? schema = n
{
var query = await ParseClrQueryAsync(context, q, schema, ct);

await TransformFilterAsync(query, context, schema, ct);
var shouldCancel = await TransformFilterAsync(query, context, schema, ct);
if (shouldCancel)
{
return null;
}

WithSorting(query);
WithPaging(query, q);
Expand All @@ -65,48 +69,71 @@ public virtual async Task<Q> ParseAsync(Context context, Q q, Schema? schema = n
}
}

private async Task TransformFilterAsync(ClrQuery query, Context context, Schema? schema,
private async Task<bool> TransformFilterAsync(ClrQuery query, Context context, Schema? schema,
CancellationToken ct)
{
if (query.Filter != null && schema != null)
{
query.Filter = await GeoQueryTransformer.TransformAsync(query.Filter, context, schema, textIndex, ct);
query.Filter = await GeoQueryVisitor.VisitAsync(query.Filter, context, schema, textIndex, ct);
}

if (string.IsNullOrWhiteSpace(query.FullText))
{
return;
return false;
}

if (schema == null)
{
ThrowHelper.InvalidOperationException();
return;
return false;
}

var searchFilters = new List<CompareFilter<ClrValue>>();

var textQuery = new TextQuery(query.FullText, 1000)
{
PreferredSchemaId = schema.Id,
};

var fullTextIds = await textIndex.SearchAsync(context.App, textQuery, context.Scope(), ct);
var fullTextFilter = ClrFilter.Eq("id", "__notfound__");
if (fullTextIds is not { Count: > 0 } && schema.Properties.SearchFields is not { Count: > 0 })
{
// Cancel the search. We would not any results.
return true;
}

if (fullTextIds?.Count > 0)
{
fullTextFilter = ClrFilter.In("id", fullTextIds.Select(x => x.ToString()).ToList());
searchFilters.Add(ClrFilter.In("id", fullTextIds.Select(x => x.ToString()).ToList()));
}

foreach (var searchField in schema.Properties.SearchFields ?? FieldNames.Empty)
{
searchFilters.Add(ClrFilter.Contains(searchField, query.FullText));
}

if (searchFilters.Count == 0)
{
return false;
}

// Just an mini optimization to flatten OR expressions with one element.
var searchFilter =
searchFilters.Count == 1 ?
searchFilters[0] :
ClrFilter.Or(searchFilters) as FilterNode<ClrValue>;

if (query.Filter != null)
{
query.Filter = ClrFilter.And(query.Filter, fullTextFilter);
query.Filter = ClrFilter.And(query.Filter, searchFilter);
}
else
{
query.Filter = fullTextFilter;
query.Filter = searchFilter;
}

query.FullText = null;
return false;
}

private async Task<ClrQuery> ParseClrQueryAsync(Context context, Q q, Schema? schema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,16 @@ public async Task<IResultList<EnrichedContent>> QueryAsync(Context context, stri
q = q with { CreatedBy = context.UserPrincipal.Token() };
}

q = await ParseCoreAsync(context, q, schema, ct);

var contents = await QueryCoreAsync(context, q, schema, ct);
var parsedQuery = await ParseCoreAsync(context, q, schema, ct);
if (parsedQuery == null)
{
return ResultList.Empty<EnrichedContent>();
}

if (q.Ids is { Count: > 0 })
var contents = await QueryCoreAsync(context, parsedQuery, schema, ct);
if (parsedQuery.Ids is { Count: > 0 })
{
contents = contents.Sorted(x => x.Id, q.Ids);
contents = contents.Sorted(x => x.Id, parsedQuery.Ids);
}

return await TransformAsync(context, contents, ct);
Expand Down Expand Up @@ -147,13 +150,16 @@ public async Task<IResultList<EnrichedContent>> QueryAsync(Context context, Q q,
return ResultList.Empty<EnrichedContent>();
}

q = await ParseCoreAsync(context, q, null, ct);

var contents = await QueryCoreAsync(context, q, schemas, ct);
var parsedQuery = await ParseCoreAsync(context, q, null, ct);
if (parsedQuery == null)
{
return ResultList.Empty<EnrichedContent>();
}

if (q.Ids is { Count: > 0 })
var contents = await QueryCoreAsync(context, parsedQuery, schemas, ct);
if (parsedQuery.Ids is { Count: > 0 })
{
contents = contents.Sorted(x => x.Id, q.Ids);
contents = contents.Sorted(x => x.Id, parsedQuery.Ids);
}

return await TransformAsync(context, contents, ct);
Expand Down Expand Up @@ -228,7 +234,7 @@ private async Task<List<Schema>> GetSchemasAsync(Context context,
return schemas.Where(x => IsAccessible(x) && HasPermission(context, x, PermissionIds.AppContentsReadOwn)).ToList();
}

private async Task<Q> ParseCoreAsync(Context context, Q q, Schema? schema,
private async Task<Q?> ParseCoreAsync(Context context, Q q, Schema? schema,
CancellationToken ct)
{
using (var combined = CancellationTokenSource.CreateLinkedTokenSource(ct))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@

namespace Squidex.Domain.Apps.Entities.Contents.Queries;

internal sealed class GeoQueryTransformer : AsyncTransformVisitor<ClrValue, GeoQueryTransformer.Args>
internal sealed class GeoQueryVisitor : AsyncTransformVisitor<ClrValue, GeoQueryVisitor.Args>
{
public static readonly GeoQueryTransformer Instance = new GeoQueryTransformer();
public static readonly GeoQueryVisitor Instance = new GeoQueryVisitor();

public record struct Args(Context Context, Schema Schema, ITextIndex TextIndex, CancellationToken CancellationToken);

private GeoQueryTransformer()
private GeoQueryVisitor()
{
}

public static async Task<FilterNode<ClrValue>?> TransformAsync(FilterNode<ClrValue> filter, Context context, Schema schema, ITextIndex textIndex,
public static async Task<FilterNode<ClrValue>?> VisitAsync(FilterNode<ClrValue> filter, Context context, Schema schema, ITextIndex textIndex,
CancellationToken ct)
{
var args = new Args(context, schema, textIndex, ct);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@ public static void CanConfigureFieldRules(ConfigureFieldRules command)
});
}

public static void CanUpdateSchema(UpdateSchema command)
{
Guard.NotNull(command);

Validate.It(e =>
{
if (command.Properties.SearchFields != null && command.Properties.SearchFields.Count > 3)
{
e(Not.Between("Size", 1, 3), "Properties.SearchFields");
}
});
}

private static void ValidateUpsert(IUpsertCommand command, AddValidation e)
{
if (command.Fields?.Length > 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ public override Task<CommandResult> ExecuteAsync(IAggregateCommand command,
case UpdateSchema update:
return ApplyReturn(update, c =>
{
GuardSchema.CanUpdateSchema(c);

Update(c);

return Snapshot;
Expand Down
3 changes: 2 additions & 1 deletion backend/src/Squidex.Infrastructure/Caching/QueryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

namespace Squidex.Infrastructure.Caching;

public class QueryCache<TKey, T>(IMemoryCache? cacheStore = null, string? cacheKeyPrefix = null) : IQueryCache<TKey, T> where TKey : notnull
public class QueryCache<TKey, T>(IMemoryCache? cacheStore = null, string? cacheKeyPrefix = null)
: IQueryCache<TKey, T> where TKey : notnull
{
public void Set(TKey key, T item, TimeSpan cacheDuration)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public static ClrQuery Convert(this QueryModel model, Query<JsonValue> query)
var result = SimpleMapper.Map(query, new ClrQuery());

var errors = new List<string>();

model.ConvertSorting(result, errors);
model.ConvertFilters(result, errors, query);

Expand All @@ -56,7 +55,6 @@ private static void ConvertFilters(this QueryModel model, ClrQuery result, List<
}

var filter = JsonFilterVisitor.Parse(query.Filter, model, errors);

if (filter != null)
{
result.Filter = Optimizer<ClrValue>.Optimize(filter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Validation;

Expand Down Expand Up @@ -54,6 +55,11 @@ public sealed class SchemaPropertiesDto
/// </summary>
public bool ValidateOnPublish { get; set; }

/// <summary>
/// The fields for automation processes.
/// </summary>
public FieldNames? SearchFields { get; init; }

/// <summary>
/// Tags for automation processes.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public sealed class UpdateSchemaDto
/// </summary>
public bool ValidateOnPublish { get; set; }

/// <summary>
/// The fields for automation processes.
/// </summary>
public FieldNames? SearchFields { get; init; }

/// <summary>
/// Tags for automation processes.
/// </summary>
Expand Down
Loading
Loading