Browse Source

Rule formatter improvements and url generator alignment. (#498)

pull/499/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
28fe82b480
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs
  2. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
  3. 14
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/IAssetUrlGenerator.cs
  4. 198
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  5. 10
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Scripting/EventScriptExtension.cs
  6. 11
      backend/src/Squidex.Domain.Apps.Core.Operations/IUrlGenerator.cs
  7. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs
  8. 3
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs
  9. 5
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  10. 10
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs
  11. 26
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLUrlGenerator.cs
  12. 11
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs
  13. 12
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs
  14. 14
      backend/src/Squidex.Domain.Apps.Entities/IEmailUrlGenerator.cs
  15. 11
      backend/src/Squidex.Domain.Apps.Entities/Notifications/NotificationEmailSender.cs
  16. 45
      backend/src/Squidex.Web/Services/UrlGenerator.cs
  17. 4
      backend/src/Squidex/Config/Domain/QueryServices.cs
  18. 45
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs
  19. 87
      backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs
  20. 2
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs
  21. 5
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ConvertDataTests.cs
  22. 9
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveAssetsTests.cs
  23. 121
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs
  24. 7
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Notifications/NotificationEmailSenderTests.cs

10
backend/src/Squidex.Domain.Apps.Core.Model/Rules/EnrichedEvents/EnrichedAssetEvent.cs

@ -7,6 +7,7 @@
using System; using System;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
@ -33,12 +34,17 @@ namespace Squidex.Domain.Apps.Core.Rules.EnrichedEvents
public long FileSize { get; set; } public long FileSize { get; set; }
public bool IsImage { get; set; }
public int? PixelWidth { get; set; } public int? PixelWidth { get; set; }
public int? PixelHeight { get; set; } public int? PixelHeight { get; set; }
public AssetType AssetType { get; set; }
public bool IsImage
{
get { return AssetType == AssetType.Image; }
}
public override long Partition public override long Partition
{ {
get { return Id.GetHashCode(); } get { return Id.GetHashCode(); }

4
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs

@ -85,7 +85,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
}; };
} }
public static FieldConverter ResolveAssetUrls(IReadOnlyCollection<string>? fields, IAssetUrlGenerator urlGenerator) public static FieldConverter ResolveAssetUrls(IReadOnlyCollection<string>? fields, IUrlGenerator urlGenerator)
{ {
if (fields?.Any() != true) if (fields?.Any() != true)
{ {
@ -122,7 +122,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
var id = array[i].ToString(); var id = array[i].ToString();
array[i] = JsonValue.Create(urlGenerator.GenerateUrl(id)); array[i] = JsonValue.Create(urlGenerator.AssetContent(Guid.Parse(id)));
} }
} }
} }

14
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/IAssetUrlGenerator.cs

@ -1,14 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Core.ConvertContent
{
public interface IAssetUrlGenerator
{
string GenerateUrl(string assetId);
}
}

198
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -26,11 +27,9 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private const string Fallback = "null"; private const string Fallback = "null";
private const string ScriptSuffix = ")"; private const string ScriptSuffix = ")";
private const string ScriptPrefix = "Script("; private const string ScriptPrefix = "Script(";
private static readonly char[] ContentPlaceholderStartOld = "CONTENT_DATA".ToCharArray(); private static readonly Regex RegexPatternOld = new Regex(@"^(?<Type>[^_]*)_(?<Path>.*)", RegexOptions.Compiled);
private static readonly char[] ContentPlaceholderStartNew = "{CONTENT_DATA".ToCharArray(); private static readonly Regex RegexPatternNew = new Regex(@"^\{(?<Type>[^_]*)_(?<Path>.*)\}", RegexOptions.Compiled);
private static readonly Regex ContentDataPlaceholderOld = new Regex(@"^CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled); private readonly List<(char[] Pattern, Func<EnrichedEvent, string?> Replacer)> patterns = new List<(char[] Pattern, Func<EnrichedEvent, string?> Replacer)>();
private static readonly Regex ContentDataPlaceholderNew = new Regex(@"^\{CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}\}", RegexOptions.Compiled);
private readonly List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)> patterns = new List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)>();
private readonly IJsonSerializer jsonSerializer; private readonly IJsonSerializer jsonSerializer;
private readonly IUrlGenerator urlGenerator; private readonly IUrlGenerator urlGenerator;
private readonly IScriptEngine scriptEngine; private readonly IScriptEngine scriptEngine;
@ -47,8 +46,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
AddPattern("APP_ID", AppId); AddPattern("APP_ID", AppId);
AddPattern("APP_NAME", AppName); AddPattern("APP_NAME", AppName);
AddPattern("ASSET_CONTENT_URL", AssetContentUrl);
AddPattern("CONTENT_ACTION", ContentAction); AddPattern("CONTENT_ACTION", ContentAction);
AddPattern("CONTENT_STATUS", ContentStatus);
AddPattern("CONTENT_URL", ContentUrl); AddPattern("CONTENT_URL", ContentUrl);
AddPattern("MENTIONED_ID", MentionedId); AddPattern("MENTIONED_ID", MentionedId);
AddPattern("MENTIONED_NAME", MentionedName); AddPattern("MENTIONED_NAME", MentionedName);
@ -62,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
AddPattern("USER_EMAIL", UserEmail); AddPattern("USER_EMAIL", UserEmail);
} }
private void AddPattern(string placeholder, Func<EnrichedEvent, string> generator) private void AddPattern(string placeholder, Func<EnrichedEvent, string?> generator)
{ {
patterns.Add((placeholder.ToCharArray(), generator)); patterns.Add((placeholder.ToCharArray(), generator));
} }
@ -102,9 +101,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var sb = new StringBuilder(); var sb = new StringBuilder();
var cp2 = new ReadOnlySpan<char>(ContentPlaceholderStartNew);
var cp1 = new ReadOnlySpan<char>(ContentPlaceholderStartOld);
for (var i = 0; i < current.Length; i++) for (var i = 0; i < current.Length; i++)
{ {
var c = current[i]; var c = current[i];
@ -115,50 +111,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules
current = current.Slice(i); current = current.Slice(i);
var test = current.Slice(1); var (replacement, length) = GetReplacement(current.Slice(1), @event);
var tested = false;
for (var j = 0; j < patterns.Count; j++) if (length > 0)
{ {
var (pattern, replacer) = patterns[j]; sb.Append(replacement);
if (test.StartsWith(pattern, StringComparison.OrdinalIgnoreCase))
{
sb.Append(replacer(@event));
current = current.Slice(pattern.Length + 1);
i = 0;
tested = true;
break;
}
}
if (!tested && (test.StartsWith(cp1, StringComparison.OrdinalIgnoreCase) || test.StartsWith(cp2, StringComparison.OrdinalIgnoreCase))) current = current.Slice(length + 1);
{ i = 0;
var currentString = test.ToString();
var match = ContentDataPlaceholderOld.Match(currentString);
if (!match.Success)
{
match = ContentDataPlaceholderNew.Match(currentString);
}
if (match.Success)
{
if (@event is EnrichedContentEvent contentEvent)
{
sb.Append(CalculateData(contentEvent.Data, match));
}
else
{
sb.Append(Fallback);
}
current = current.Slice(match.Length + 1);
i = 0;
}
} }
} }
} }
@ -168,6 +128,37 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return sb.ToString(); return sb.ToString();
} }
private (string Result, int Length) GetReplacement(ReadOnlySpan<char> test, EnrichedEvent @event)
{
for (var j = 0; j < patterns.Count; j++)
{
var (pattern, replacer) = patterns[j];
if (test.StartsWith(pattern, StringComparison.OrdinalIgnoreCase))
{
return (replacer(@event) ?? Fallback, pattern.Length);
}
}
var currentString = test.ToString();
var match = RegexPatternNew.Match(currentString);
if (!match.Success)
{
match = RegexPatternOld.Match(currentString);
}
if (match.Success)
{
var path = match.Groups["Path"].Value.Split('.', StringSplitOptions.RemoveEmptyEntries);
return (CalculateData(@event, path) ?? Fallback, match.Length);
}
return (Fallback, 0);
}
private static string TimestampDate(EnrichedEvent @event) private static string TimestampDate(EnrichedEvent @event)
{ {
return @event.Timestamp.ToDateTimeUtc().ToString("yyy-MM-dd", CultureInfo.InvariantCulture); return @event.Timestamp.ToDateTimeUtc().ToString("yyy-MM-dd", CultureInfo.InvariantCulture);
@ -188,74 +179,84 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return @event.AppId.Name; return @event.AppId.Name;
} }
private static string SchemaId(EnrichedEvent @event) private static string? SchemaId(EnrichedEvent @event)
{ {
if (@event is EnrichedSchemaEventBase schemaEvent) if (@event is EnrichedSchemaEventBase schemaEvent)
{ {
return schemaEvent.SchemaId.Id.ToString(); return schemaEvent.SchemaId.Id.ToString();
} }
return Fallback; return null;
} }
private static string SchemaName(EnrichedEvent @event) private static string? SchemaName(EnrichedEvent @event)
{ {
if (@event is EnrichedSchemaEventBase schemaEvent) if (@event is EnrichedSchemaEventBase schemaEvent)
{ {
return schemaEvent.SchemaId.Name; return schemaEvent.SchemaId.Name;
} }
return Fallback; return null;
} }
private static string ContentAction(EnrichedEvent @event) private static string? ContentAction(EnrichedEvent @event)
{ {
if (@event is EnrichedContentEvent contentEvent) if (@event is EnrichedContentEvent contentEvent)
{ {
return contentEvent.Type.ToString(); return contentEvent.Type.ToString();
} }
return Fallback; return null;
} }
private static string ContentStatus(EnrichedEvent @event) private static string? ContentStatus(EnrichedEvent @event)
{ {
if (@event is EnrichedContentEvent contentEvent) if (@event is EnrichedContentEvent contentEvent)
{ {
return contentEvent.Status.ToString(); return contentEvent.Status.ToString();
} }
return Fallback; return null;
} }
private string ContentUrl(EnrichedEvent @event) private string? AssetContentUrl(EnrichedEvent @event)
{
if (@event is EnrichedAssetEvent assetEvent)
{
return urlGenerator.AssetContent(assetEvent.Id);
}
return null;
}
private string? ContentUrl(EnrichedEvent @event)
{ {
if (@event is EnrichedContentEvent contentEvent) if (@event is EnrichedContentEvent contentEvent)
{ {
return urlGenerator.ContentUI(contentEvent.AppId, contentEvent.SchemaId, contentEvent.Id); return urlGenerator.ContentUI(contentEvent.AppId, contentEvent.SchemaId, contentEvent.Id);
} }
return Fallback; return null;
} }
private static string UserName(EnrichedEvent @event) private static string? UserName(EnrichedEvent @event)
{ {
if (@event is EnrichedUserEventBase userEvent) if (@event is EnrichedUserEventBase userEvent)
{ {
return userEvent.User?.DisplayName() ?? Fallback; return userEvent.User?.DisplayName() ?? Fallback;
} }
return Fallback; return null;
} }
private static string UserId(EnrichedEvent @event) private static string? UserId(EnrichedEvent @event)
{ {
if (@event is EnrichedUserEventBase userEvent) if (@event is EnrichedUserEventBase userEvent)
{ {
return userEvent.User?.Id ?? Fallback; return userEvent.User?.Id ?? Fallback;
} }
return Fallback; return null;
} }
private static string UserEmail(EnrichedEvent @event) private static string UserEmail(EnrichedEvent @event)
@ -298,36 +299,63 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return Fallback; return Fallback;
} }
private static string CalculateData(NamedContentData data, Match match) private static string? CalculateData(object @event, string[] path)
{ {
var captures = match.Groups[2].Captures; object? current = @event;
var path = new string[captures.Count];
for (var i = 0; i < path.Length; i++) foreach (var segment in path)
{ {
path[i] = captures[i].Value; if (current is NamedContentData data)
} {
if (!data.TryGetValue(segment, out var temp) || temp == null)
{
return null;
}
if (!data.TryGetValue(path[0], out var field) || field == null) current = temp;
{ }
return Fallback; else if (current is ContentFieldData field)
} {
if (!field.TryGetValue(segment, out var temp) || temp == null)
{
return null;
}
if (!field.TryGetValue(path[1], out var value)) current = temp;
{ }
return Fallback; else if (current is IJsonValue json)
} {
if (!json.TryGet(segment, out var temp) || temp == null || temp.Type == JsonValueType.Null)
{
return null;
}
if (path.Skip(2).Any()) current = temp;
{ }
if (!value.TryGetByPath(path.Skip(2), out value) || value == null || value.Type == JsonValueType.Null) else if (current != null)
{
const BindingFlags bindingFlags =
BindingFlags.FlattenHierarchy |
BindingFlags.Public |
BindingFlags.Instance;
var properties = current.GetType().GetProperties(bindingFlags);
var property = properties.FirstOrDefault(x => x.CanRead && string.Equals(x.Name, segment, StringComparison.OrdinalIgnoreCase));
if (property == null)
{
return null;
}
current = property.GetValue(current);
}
else
{ {
return Fallback; return null;
} }
} }
return value.ToString() ?? Fallback; return current?.ToString();
} }
} }
} }

10
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Scripting/EventScriptExtension.cs

@ -45,6 +45,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Scripting
return JsValue.Null; return JsValue.Null;
})); }));
context.Engine.SetValue("assetContentUrl", new EventDelegate(() =>
{
if (context.TryGetValue("event", out var temp) && temp is EnrichedAssetEvent assetEvent)
{
return urlGenerator.AssetContent(assetEvent.Id);
}
return JsValue.Null;
}));
} }
} }
} }

11
backend/src/Squidex.Domain.Apps.Core.Operations/IUrlGenerator.cs

@ -6,18 +6,27 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core namespace Squidex.Domain.Apps.Core
{ {
public interface IUrlGenerator public interface IUrlGenerator
{ {
bool CanGenerateAssetSourceUrl { get; }
string? AssetSource(Guid assetId, long fileVersion);
string? AssetThumbnail(Guid assetId, AssetType assetType);
string AppSettingsUI(NamedId<Guid> appId); string AppSettingsUI(NamedId<Guid> appId);
string AssetsUI(NamedId<Guid> appId); string AssetsUI(NamedId<Guid> appId);
string AssetsUI(NamedId<Guid> appId, string? query = null); string AssetsUI(NamedId<Guid> appId, string? query = null);
string AssetContent(Guid assetId);
string BackupsUI(NamedId<Guid> appId); string BackupsUI(NamedId<Guid> appId);
string ClientsUI(NamedId<Guid> appId); string ClientsUI(NamedId<Guid> appId);
@ -47,5 +56,7 @@ namespace Squidex.Domain.Apps.Core
string SchemaUI(NamedId<Guid> appId, NamedId<Guid> schemaId); string SchemaUI(NamedId<Guid> appId, NamedId<Guid> schemaId);
string WorkflowsUI(NamedId<Guid> appId); string WorkflowsUI(NamedId<Guid> appId);
string UI();
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs

@ -45,6 +45,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
SimpleMapper.Map(asset, result); SimpleMapper.Map(asset, result);
result.AssetType = asset.Type;
switch (@event.Payload) switch (@event.Payload)
{ {
case AssetCreated _: case AssetCreated _:

3
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs

@ -11,6 +11,7 @@ using System.Threading.Tasks;
using GraphQL; using GraphQL;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -92,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
allSchemas, allSchemas,
GetPageSizeForContents(), GetPageSizeForContents(),
GetPageSizeForAssets(), GetPageSizeForAssets(),
resolver.Resolve<IGraphQLUrlGenerator>()); resolver.Resolve<IUrlGenerator>());
}); });
} }

5
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using GraphQL; using GraphQL;
using GraphQL.DataLoader; using GraphQL.DataLoader;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
using Squidex.Domain.Apps.Entities.Contents.Queries; using Squidex.Domain.Apps.Entities.Contents.Queries;
@ -26,7 +27,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
private readonly IDataLoaderContextAccessor dataLoaderContextAccessor; private readonly IDataLoaderContextAccessor dataLoaderContextAccessor;
private readonly IDependencyResolver resolver; private readonly IDependencyResolver resolver;
public IGraphQLUrlGenerator UrlGenerator { get; } public IUrlGenerator UrlGenerator { get; }
public ISemanticLog Log { get; } public ISemanticLog Log { get; }
@ -37,7 +38,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
resolver.Resolve<IAssetQueryService>(), resolver.Resolve<IAssetQueryService>(),
resolver.Resolve<IContentQueryService>()) resolver.Resolve<IContentQueryService>())
{ {
UrlGenerator = resolver.Resolve<IGraphQLUrlGenerator>(); UrlGenerator = resolver.Resolve<IUrlGenerator>();
dataLoaderContextAccessor = resolver.Resolve<IDataLoaderContextAccessor>(); dataLoaderContextAccessor = resolver.Resolve<IDataLoaderContextAccessor>();

10
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs

@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
IEnumerable<ISchemaEntity> schemas, IEnumerable<ISchemaEntity> schemas,
int pageSizeContents, int pageSizeContents,
int pageSizeAssets, int pageSizeAssets,
IGraphQLUrlGenerator urlGenerator) IUrlGenerator urlGenerator)
{ {
this.app = app; this.app = app;
@ -99,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
var context = (GraphQLExecutionContext)c.UserContext; var context = (GraphQLExecutionContext)c.UserContext;
return context.UrlGenerator.GenerateAssetUrl(app, c.Source); return context.UrlGenerator.AssetContent(c.Source.Id);
}); });
return resolver; return resolver;
@ -111,7 +111,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
var context = (GraphQLExecutionContext)c.UserContext; var context = (GraphQLExecutionContext)c.UserContext;
return context.UrlGenerator.GenerateAssetSourceUrl(c.Source); return context.UrlGenerator.AssetSource(c.Source.Id, c.Source.FileVersion);
}); });
return resolver; return resolver;
@ -123,7 +123,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
var context = (GraphQLExecutionContext)c.UserContext; var context = (GraphQLExecutionContext)c.UserContext;
return context.UrlGenerator.GenerateAssetThumbnailUrl(app, c.Source); return context.UrlGenerator.AssetThumbnail(c.Source.Id, c.Source.Type);
}); });
return resolver; return resolver;
@ -135,7 +135,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{ {
var context = (GraphQLExecutionContext)c.UserContext; var context = (GraphQLExecutionContext)c.UserContext;
return context.UrlGenerator.GenerateContentUrl(app, schema, c.Source); return context.UrlGenerator.ContentUI(app.NamedId(), schema.NamedId(), c.Source.Id);
}); });
return resolver; return resolver;

26
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/IGraphQLUrlGenerator.cs

@ -1,26 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public interface IGraphQLUrlGenerator
{
bool CanGenerateAssetSourceUrl { get; }
string? GenerateAssetThumbnailUrl(IAppEntity app, IAssetEntity asset);
string? GenerateAssetSourceUrl(IAssetEntity asset);
string GenerateAssetUrl(IAppEntity app, IAssetEntity asset);
string GenerateContentUrl(IAppEntity app, ISchemaEntity schema, IContentEntity content);
}
}

11
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ConvertData.cs

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
@ -19,17 +20,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{ {
public sealed class ConvertData : IContentEnricherStep public sealed class ConvertData : IContentEnricherStep
{ {
private readonly IAssetUrlGenerator assetUrlGenerator; private readonly IUrlGenerator urlGenerator;
private readonly IAssetRepository assetRepository; private readonly IAssetRepository assetRepository;
private readonly IContentRepository contentRepository; private readonly IContentRepository contentRepository;
public ConvertData(IAssetUrlGenerator assetUrlGenerator, IAssetRepository assetRepository, IContentRepository contentRepository) public ConvertData(IUrlGenerator urlGenerator, IAssetRepository assetRepository, IContentRepository contentRepository)
{ {
Guard.NotNull(assetUrlGenerator); Guard.NotNull(urlGenerator);
Guard.NotNull(assetRepository); Guard.NotNull(assetRepository);
Guard.NotNull(contentRepository); Guard.NotNull(contentRepository);
this.assetUrlGenerator = assetUrlGenerator; this.urlGenerator = urlGenerator;
this.assetRepository = assetRepository; this.assetRepository = assetRepository;
this.contentRepository = contentRepository; this.contentRepository = contentRepository;
} }
@ -137,7 +138,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
if (assetUrls.Any()) if (assetUrls.Any())
{ {
yield return FieldConverters.ResolveAssetUrls(assetUrls.ToList(), assetUrlGenerator); yield return FieldConverters.ResolveAssetUrls(assetUrls.ToList(), urlGenerator);
} }
} }
} }

12
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs

@ -9,9 +9,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
@ -26,17 +26,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{ {
private static readonly ILookup<Guid, IEnrichedAssetEntity> EmptyAssets = Enumerable.Empty<IEnrichedAssetEntity>().ToLookup(x => x.Id); private static readonly ILookup<Guid, IEnrichedAssetEntity> EmptyAssets = Enumerable.Empty<IEnrichedAssetEntity>().ToLookup(x => x.Id);
private readonly IAssetUrlGenerator assetUrlGenerator; private readonly IUrlGenerator urlGenerator;
private readonly IAssetQueryService assetQuery; private readonly IAssetQueryService assetQuery;
private readonly IRequestCache requestCache; private readonly IRequestCache requestCache;
public ResolveAssets(IAssetUrlGenerator assetUrlGenerator, IAssetQueryService assetQuery, IRequestCache requestCache) public ResolveAssets(IUrlGenerator urlGenerator, IAssetQueryService assetQuery, IRequestCache requestCache)
{ {
Guard.NotNull(assetUrlGenerator); Guard.NotNull(urlGenerator);
Guard.NotNull(assetQuery); Guard.NotNull(assetQuery);
Guard.NotNull(requestCache); Guard.NotNull(requestCache);
this.assetUrlGenerator = assetUrlGenerator; this.urlGenerator = urlGenerator;
this.assetQuery = assetQuery; this.assetQuery = assetQuery;
this.requestCache = requestCache; this.requestCache = requestCache;
} }
@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
if (referencedImage != null) if (referencedImage != null)
{ {
var url = assetUrlGenerator.GenerateUrl(referencedImage.Id.ToString()); var url = urlGenerator.AssetContent(Guid.Parse(referencedImage.Id.ToString()));
requestCache.AddDependency(referencedImage.Id, referencedImage.Version); requestCache.AddDependency(referencedImage.Id, referencedImage.Version);

14
backend/src/Squidex.Domain.Apps.Entities/IEmailUrlGenerator.cs

@ -1,14 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities
{
public interface IEmailUrlGenerator
{
string GenerateUIUrl();
}
}

11
backend/src/Squidex.Domain.Apps.Entities/Notifications/NotificationEmailSender.cs

@ -8,6 +8,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Email; using Squidex.Infrastructure.Email;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
@ -18,7 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Notifications
public sealed class NotificationEmailSender : INotificationSender public sealed class NotificationEmailSender : INotificationSender
{ {
private readonly IEmailSender emailSender; private readonly IEmailSender emailSender;
private readonly IEmailUrlGenerator emailUrlGenerator; private readonly IUrlGenerator urlGenerator;
private readonly ISemanticLog log; private readonly ISemanticLog log;
private readonly NotificationEmailTextOptions texts; private readonly NotificationEmailTextOptions texts;
@ -45,17 +46,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Notifications
public NotificationEmailSender( public NotificationEmailSender(
IOptions<NotificationEmailTextOptions> texts, IOptions<NotificationEmailTextOptions> texts,
IEmailSender emailSender, IEmailSender emailSender,
IEmailUrlGenerator emailUrlGenerator, IUrlGenerator urlGenerator,
ISemanticLog log) ISemanticLog log)
{ {
Guard.NotNull(texts); Guard.NotNull(texts);
Guard.NotNull(emailSender); Guard.NotNull(emailSender);
Guard.NotNull(emailUrlGenerator); Guard.NotNull(urlGenerator);
Guard.NotNull(log); Guard.NotNull(log);
this.texts = texts.Value; this.texts = texts.Value;
this.emailSender = emailSender; this.emailSender = emailSender;
this.emailUrlGenerator = emailUrlGenerator; this.urlGenerator = urlGenerator;
this.log = log; this.log = log;
} }
@ -115,7 +116,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Notifications
return; return;
} }
vars.URL = emailUrlGenerator.GenerateUIUrl(); vars.URL = urlGenerator.UI();
vars.User = user; vars.User = user;

45
backend/src/Squidex.Web/Services/UrlGenerator.cs

@ -9,18 +9,12 @@ using System;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Web.Services namespace Squidex.Web.Services
{ {
public sealed class UrlGenerator : IGraphQLUrlGenerator, IUrlGenerator, IAssetUrlGenerator, IEmailUrlGenerator public sealed class UrlGenerator : IUrlGenerator
{ {
private readonly IAssetFileStore assetFileStore; private readonly IAssetFileStore assetFileStore;
private readonly UrlsOptions urlsOptions; private readonly UrlsOptions urlsOptions;
@ -39,44 +33,29 @@ namespace Squidex.Web.Services
CanGenerateAssetSourceUrl = allowAssetSourceUrl; CanGenerateAssetSourceUrl = allowAssetSourceUrl;
} }
public string? GenerateAssetThumbnailUrl(IAppEntity app, IAssetEntity asset) public string? AssetThumbnail(Guid assetId, AssetType assetType)
{ {
if (asset.Type != AssetType.Image) if (assetType != AssetType.Image)
{ {
return null; return null;
} }
return urlsOptions.BuildUrl($"api/assets/{asset.Id}?version={asset.FileVersion}&width=100&mode=Max"); return urlsOptions.BuildUrl($"api/assets/{assetId}?width=100&mode=Max");
} }
public string GenerateUrl(string assetId) public string AppSettingsUI(NamedId<Guid> appId)
{
return urlsOptions.BuildUrl($"api/assets/{assetId}");
}
public string GenerateAssetUrl(IAppEntity app, IAssetEntity asset)
{
return urlsOptions.BuildUrl($"api/assets/{asset.Id}?version={asset.FileVersion}");
}
public string GenerateContentUrl(IAppEntity app, ISchemaEntity schema, IContentEntity content)
{
return urlsOptions.BuildUrl($"api/content/{app.Name}/{schema.SchemaDef.Name}/{content.Id}");
}
public string GenerateContentUIUrl(NamedId<Guid> appId, NamedId<Guid> schemaId, Guid contentId)
{ {
return urlsOptions.BuildUrl($"app/{appId.Name}/content/{schemaId.Name}/{contentId}/history"); return urlsOptions.BuildUrl($"app/{appId.Name}/settings", false);
} }
public string GenerateUIUrl() public string AssetContent(Guid assetId)
{ {
return urlsOptions.BuildUrl("app", false); return urlsOptions.BuildUrl($"api/assets/{assetId}");
} }
public string AppSettingsUI(NamedId<Guid> appId) public string? AssetSource(Guid assetId, long fileVersion)
{ {
return urlsOptions.BuildUrl($"app/{appId.Name}/settings", false); return assetFileStore.GeneratePublicUrl(assetId, fileVersion);
} }
public string AssetsUI(NamedId<Guid> appId) public string AssetsUI(NamedId<Guid> appId)
@ -164,9 +143,9 @@ namespace Squidex.Web.Services
return urlsOptions.BuildUrl($"app/{appId.Name}/settings/workflows", false); return urlsOptions.BuildUrl($"app/{appId.Name}/settings/workflows", false);
} }
public string? GenerateAssetSourceUrl(IAssetEntity asset) public string UI()
{ {
return assetFileStore.GeneratePublicUrl(asset.Id, asset.FileVersion); return urlsOptions.BuildUrl("app", false);
} }
} }
} }

4
backend/src/Squidex/Config/Domain/QueryServices.cs

@ -11,8 +11,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Web; using Squidex.Web;
@ -30,7 +28,7 @@ namespace Squidex.Config.Domain
c.GetRequiredService<IOptions<UrlsOptions>>(), c.GetRequiredService<IOptions<UrlsOptions>>(),
c.GetRequiredService<IAssetFileStore>(), c.GetRequiredService<IAssetFileStore>(),
exposeSourceUrl)) exposeSourceUrl))
.As<IGraphQLUrlGenerator>().As<IUrlGenerator>().As<IAssetUrlGenerator>().As<IEmailUrlGenerator>(); .As<IUrlGenerator>();
services.AddSingletonAs(x => new FuncDependencyResolver(x.GetRequiredService)) services.AddSingletonAs(x => new FuncDependencyResolver(x.GetRequiredService))
.As<IDependencyResolver>(); .As<IDependencyResolver>();

45
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FakeItEasy; using FakeItEasy;
@ -20,13 +21,15 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
{ {
public class FieldConvertersTests public class FieldConvertersTests
{ {
private readonly IAssetUrlGenerator assetUrlGenerator = A.Fake<IAssetUrlGenerator>(); private readonly IUrlGenerator urlGenerato = A.Fake<IUrlGenerator>();
private readonly Guid id1 = Guid.NewGuid();
private readonly Guid id2 = Guid.NewGuid();
private readonly LanguagesConfig languagesConfig = LanguagesConfig.English.Set(Language.DE); private readonly LanguagesConfig languagesConfig = LanguagesConfig.English.Set(Language.DE);
public FieldConvertersTests() public FieldConvertersTests()
{ {
A.CallTo(() => assetUrlGenerator.GenerateUrl(A<string>._)) A.CallTo(() => urlGenerato.AssetContent(A<Guid>._))
.ReturnsLazily(ctx => $"url/to/{ctx.GetArgument<string>(0)}"); .ReturnsLazily(ctx => $"url/to/{ctx.GetArgument<Guid>(0)}");
} }
[Fact] [Fact]
@ -479,13 +482,13 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
var source = var source =
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array("1", "2")); .AddJsonValue(JsonValue.Array(id1, id2));
var expected = var expected =
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array("url/to/1", "url/to/2")); .AddJsonValue(JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"));
var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "assets" }), assetUrlGenerator)(source, field); var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "assets" }), urlGenerato)(source, field);
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
@ -501,15 +504,15 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array( .AddJsonValue(JsonValue.Array(
JsonValue.Object() JsonValue.Object()
.Add("assets", JsonValue.Array("1", "2")))); .Add("assets", JsonValue.Array(id1, id2))));
var expected = var expected =
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array( .AddJsonValue(JsonValue.Array(
JsonValue.Object() JsonValue.Object()
.Add("assets", JsonValue.Array("url/to/1", "url/to/2")))); .Add("assets", JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"))));
var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "array.assets" }), assetUrlGenerator)(source, field); var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "array.assets" }), urlGenerato)(source, field);
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
@ -521,13 +524,13 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
var source = var source =
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array("1", "2")); .AddJsonValue(JsonValue.Array(id1, id2));
var expected = var expected =
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array("url/to/1", "url/to/2")); .AddJsonValue(JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"));
var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "*" }), assetUrlGenerator)(source, field); var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "*" }), urlGenerato)(source, field);
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
@ -543,15 +546,15 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array( .AddJsonValue(JsonValue.Array(
JsonValue.Object() JsonValue.Object()
.Add("assets", JsonValue.Array("1", "2")))); .Add("assets", JsonValue.Array(id1, id2))));
var expected = var expected =
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array( .AddJsonValue(JsonValue.Array(
JsonValue.Object() JsonValue.Object()
.Add("assets", JsonValue.Array("url/to/1", "url/to/2")))); .Add("assets", JsonValue.Array($"url/to/{id1}", $"url/to/{id2}"))));
var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "*" }), assetUrlGenerator)(source, field); var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "*" }), urlGenerato)(source, field);
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
@ -563,13 +566,13 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
var source = var source =
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array("1", "2")); .AddJsonValue(JsonValue.Array(id1, id2));
var expected = var expected =
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array("1", "2")); .AddJsonValue(JsonValue.Array(id1, id2));
var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "other" }), assetUrlGenerator)(source, field); var result = FieldConverters.ResolveAssetUrls(new HashSet<string>(new[] { "other" }), urlGenerato)(source, field);
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }
@ -581,13 +584,13 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent
var source = var source =
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array("1", "2")); .AddJsonValue(JsonValue.Array(id1, id2));
var expected = var expected =
new ContentFieldData() new ContentFieldData()
.AddJsonValue(JsonValue.Array("1", "2")); .AddJsonValue(JsonValue.Array(id1, id2));
var result = FieldConverters.ResolveAssetUrls(null, assetUrlGenerator)(source, field); var result = FieldConverters.ResolveAssetUrls(null, urlGenerato)(source, field);
Assert.Equal(expected, result); Assert.Equal(expected, result);
} }

87
backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs

@ -12,6 +12,7 @@ using FakeItEasy;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.Scripting; using Squidex.Domain.Apps.Core.HandleRules.Scripting;
@ -34,12 +35,19 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly Instant now = SystemClock.Instance.GetCurrentInstant(); private readonly Instant now = SystemClock.Instance.GetCurrentInstant();
private readonly Guid contentId = Guid.NewGuid(); private readonly Guid contentId = Guid.NewGuid();
private readonly Guid assetId = Guid.NewGuid();
private readonly RuleEventFormatter sut; private readonly RuleEventFormatter sut;
public RuleEventFormatterTests() public RuleEventFormatterTests()
{ {
A.CallTo(() => urlGenerator.ContentUI(appId, schemaId, contentId))
.Returns("content-url");
A.CallTo(() => urlGenerator.AssetContent(assetId))
.Returns("asset-content-url");
A.CallTo(() => user.Id) A.CallTo(() => user.Id)
.Returns("123"); .Returns("user123");
A.CallTo(() => user.Email) A.CallTo(() => user.Email)
.Returns("me@email.com"); .Returns("me@email.com");
@ -47,9 +55,6 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
A.CallTo(() => user.Claims) A.CallTo(() => user.Claims)
.Returns(new List<Claim> { new Claim(SquidexClaimTypes.DisplayName, "me") }); .Returns(new List<Claim> { new Claim(SquidexClaimTypes.DisplayName, "me") });
A.CallTo(() => urlGenerator.ContentUI(appId, schemaId, contentId))
.Returns("content-url");
var extensions = new IScriptExtension[] var extensions = new IScriptExtension[]
{ {
new DateTimeScriptExtension(), new DateTimeScriptExtension(),
@ -93,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Theory] [Theory]
[InlineData("Name $APP_NAME has id $APP_ID")] [InlineData("Name $APP_NAME has id $APP_ID")]
[InlineData("Script(`Name ${event.appId.name} has id ${event.appId.id}`)")] [InlineData("Script(`Name ${event.appId.name} has id ${event.appId.id}`)")]
public void Should_replace_app_information_from_event(string script) public void Should_format_app_information_from_event(string script)
{ {
var @event = new EnrichedContentEvent { AppId = appId }; var @event = new EnrichedContentEvent { AppId = appId };
@ -105,7 +110,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Theory] [Theory]
[InlineData("Name $SCHEMA_NAME has id $SCHEMA_ID")] [InlineData("Name $SCHEMA_NAME has id $SCHEMA_ID")]
[InlineData("Script(`Name ${event.schemaId.name} has id ${event.schemaId.id}`)")] [InlineData("Script(`Name ${event.schemaId.name} has id ${event.schemaId.id}`)")]
public void Should_replace_schema_information_from_event(string script) public void Should_format_schema_information_from_event(string script)
{ {
var @event = new EnrichedContentEvent { SchemaId = schemaId }; var @event = new EnrichedContentEvent { SchemaId = schemaId };
@ -117,7 +122,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Theory] [Theory]
[InlineData("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME")] [InlineData("Date: $TIMESTAMP_DATE, Full: $TIMESTAMP_DATETIME")]
[InlineData("Script(`Date: ${formatDate(event.timestamp, 'yyyy-MM-dd')}, Full: ${formatDate(event.timestamp, 'yyyy-MM-dd-hh-mm-ss')}`)")] [InlineData("Script(`Date: ${formatDate(event.timestamp, 'yyyy-MM-dd')}, Full: ${formatDate(event.timestamp, 'yyyy-MM-dd-hh-mm-ss')}`)")]
public void Should_replace_timestamp_information_from_event(string script) public void Should_format_timestamp_information_from_event(string script)
{ {
var @event = new EnrichedContentEvent { Timestamp = now }; var @event = new EnrichedContentEvent { Timestamp = now };
@ -135,7 +140,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var result = sut.Format(script, @event); var result = sut.Format(script, @event);
Assert.Equal("From me (me@email.com, 123)", result); Assert.Equal("From me (me@email.com, user123)", result);
} }
[Theory] [Theory]
@ -147,7 +152,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
var result = sut.Format(script, @event); var result = sut.Format(script, @event);
Assert.Equal("From me (me@email.com, 123)", result); Assert.Equal("From me (me@email.com, user123)", result);
} }
[Theory] [Theory]
@ -174,10 +179,70 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
Assert.Equal("From client:android (client:android, android)", result); Assert.Equal("From client:android (client:android, android)", result);
} }
[Theory]
[InlineData("Version: $ASSET_VERSION")]
[InlineData("Script(`Version: ${event.version}`)")]
public void Should_format_base_property(string script)
{
var @event = new EnrichedAssetEvent { Version = 13 };
var result = sut.Format(script, @event);
Assert.Equal("Version: 13", result);
}
[Theory]
[InlineData("File: $ASSET_FILENAME")]
[InlineData("Script(`File: ${event.fileName}`)")]
public void Should_format_asset_file_name_from_event(string script)
{
var @event = new EnrichedAssetEvent { FileName = "my-file.png" };
var result = sut.Format(script, @event);
Assert.Equal("File: my-file.png", result);
}
[Theory]
[InlineData("Type: $ASSET_ASSETTYPE")]
[InlineData("Script(`Type: ${event.assetType}`)")]
public void Should_format_asset_asset_type_from_event(string script)
{
var @event = new EnrichedAssetEvent { AssetType = AssetType.Audio };
var result = sut.Format(script, @event);
Assert.Equal("Type: Audio", result);
}
[Theory]
[InlineData("Download at $ASSET_CONTENT_URL")]
[InlineData("Script(`Download at ${assetContentUrl()}`)")]
public void Should_format_asset_content_url_from_event(string script)
{
var @event = new EnrichedAssetEvent { Id = assetId };
var result = sut.Format(script, @event);
Assert.Equal("Download at asset-content-url", result);
}
[Theory]
[InlineData("Download at $ASSET_CONTENT_URL")]
[InlineData("Script(`Download at ${assetContentUrl()}`)")]
public void Should_return_null_when_asset_content_url_not_found(string script)
{
var @event = new EnrichedContentEvent();
var result = sut.Format(script, @event);
Assert.Equal("Download at null", result);
}
[Theory] [Theory]
[InlineData("Go to $CONTENT_URL")] [InlineData("Go to $CONTENT_URL")]
[InlineData("Script(`Go to ${contentUrl()}`)")] [InlineData("Script(`Go to ${contentUrl()}`)")]
public void Should_replace_content_url_from_event(string script) public void Should_format_content_url_from_event(string script)
{ {
var @event = new EnrichedContentEvent { AppId = appId, Id = contentId, SchemaId = schemaId }; var @event = new EnrichedContentEvent { AppId = appId, Id = contentId, SchemaId = schemaId };
@ -189,7 +254,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules
[Theory] [Theory]
[InlineData("Go to $CONTENT_URL")] [InlineData("Go to $CONTENT_URL")]
[InlineData("Script(`Go to ${contentUrl()}`)")] [InlineData("Script(`Go to ${contentUrl()}`)")]
public void Should_format_content_url_when_not_found(string script) public void Should_return_null_when_content_url_when_not_found(string script)
{ {
var @event = new EnrichedAssetEvent(); var @event = new EnrichedAssetEvent();

2
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

@ -272,10 +272,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
[typeof(IAssetQueryService)] = assetQuery, [typeof(IAssetQueryService)] = assetQuery,
[typeof(IContentQueryService)] = contentQuery, [typeof(IContentQueryService)] = contentQuery,
[typeof(IDataLoaderContextAccessor)] = dataLoaderContext, [typeof(IDataLoaderContextAccessor)] = dataLoaderContext,
[typeof(IGraphQLUrlGenerator)] = new FakeUrlGenerator(),
[typeof(IOptions<AssetOptions>)] = Options.Create(new AssetOptions()), [typeof(IOptions<AssetOptions>)] = Options.Create(new AssetOptions()),
[typeof(IOptions<ContentOptions>)] = Options.Create(new ContentOptions()), [typeof(IOptions<ContentOptions>)] = Options.Create(new ContentOptions()),
[typeof(ISemanticLog)] = A.Fake<ISemanticLog>(), [typeof(ISemanticLog)] = A.Fake<ISemanticLog>(),
[typeof(IUrlGenerator)] = new FakeUrlGenerator(),
[typeof(DataLoaderDocumentListener)] = new DataLoaderDocumentListener(dataLoaderContext) [typeof(DataLoaderDocumentListener)] = new DataLoaderDocumentListener(dataLoaderContext)
}; };

5
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ConvertDataTests.cs

@ -12,7 +12,6 @@ using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps; using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
@ -28,7 +27,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public class ConvertDataTests public class ConvertDataTests
{ {
private readonly ISchemaEntity schema; private readonly ISchemaEntity schema;
private readonly IAssetUrlGenerator assetUrlGenerator = A.Fake<IAssetUrlGenerator>(); private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>(); private readonly IAssetRepository assetRepository = A.Fake<IAssetRepository>();
private readonly IContentRepository contentRepository = A.Fake<IContentRepository>(); private readonly IContentRepository contentRepository = A.Fake<IContentRepository>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
@ -48,7 +47,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
schema = Mocks.Schema(appId, schemaId, schemaDef); schema = Mocks.Schema(appId, schemaId, schemaDef);
schemaProvider = x => Task.FromResult(schema); schemaProvider = x => Task.FromResult(schema);
sut = new ConvertData(assetUrlGenerator, assetRepository, contentRepository); sut = new ConvertData(urlGenerator, assetRepository, contentRepository);
} }
[Fact] [Fact]

9
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ResolveAssetsTests.cs

@ -12,7 +12,6 @@ using FakeItEasy;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Contents.Queries.Steps; using Squidex.Domain.Apps.Entities.Contents.Queries.Steps;
@ -28,7 +27,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public class ResolveAssetsTests public class ResolveAssetsTests
{ {
private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>(); private readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
private readonly IAssetUrlGenerator assetUrlGenerator = A.Fake<IAssetUrlGenerator>(); private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly IRequestCache requestCache = A.Fake<IRequestCache>(); private readonly IRequestCache requestCache = A.Fake<IRequestCache>();
private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app"); private readonly NamedId<Guid> appId = NamedId.Of(Guid.NewGuid(), "my-app");
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
@ -56,8 +55,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
}) })
.SetFieldsInLists("asset1", "asset2"); .SetFieldsInLists("asset1", "asset2");
A.CallTo(() => assetUrlGenerator.GenerateUrl(A<string>._)) A.CallTo(() => urlGenerator.AssetContent(A<Guid>._))
.ReturnsLazily(new Func<string, string>(id => $"url/to/{id}")); .ReturnsLazily(ctx => $"url/to/{ctx.GetArgument<Guid>(0)}");
schemaProvider = x => schemaProvider = x =>
{ {
@ -71,7 +70,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
} }
}; };
sut = new ResolveAssets(assetUrlGenerator, assetQuery, requestCache); sut = new ResolveAssets(urlGenerator, assetQuery, requestCache);
} }
[Fact] [Fact]

121
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs

@ -5,35 +5,130 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Domain.Apps.Entities.Apps; using System;
using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.TestData namespace Squidex.Domain.Apps.Entities.Contents.TestData
{ {
public sealed class FakeUrlGenerator : IGraphQLUrlGenerator public sealed class FakeUrlGenerator : IUrlGenerator
{ {
public bool CanGenerateAssetSourceUrl { get; } = true; public bool CanGenerateAssetSourceUrl { get; } = true;
public string GenerateAssetUrl(IAppEntity app, IAssetEntity asset) public string? AssetThumbnail(Guid assetId, AssetType assetType)
{ {
return $"assets/{asset.Id}"; return $"assets/{assetId}?width=100";
} }
public string GenerateAssetThumbnailUrl(IAppEntity app, IAssetEntity asset) public string? AssetSource(Guid assetId, long fileVersion)
{ {
return $"assets/{asset.Id}?width=100"; return $"assets/source/{assetId}";
} }
public string GenerateAssetSourceUrl(IAssetEntity asset) public string AssetContent(Guid assetId)
{ {
return $"assets/source/{asset.Id}"; return $"assets/{assetId}";
} }
public string GenerateContentUrl(IAppEntity app, ISchemaEntity schema, IContentEntity content) public string ContentUI(NamedId<Guid> appId, NamedId<Guid> schemaId, Guid contentId)
{ {
return $"contents/{schema.SchemaDef.Name}/{content.Id}"; return $"contents/{schemaId.Name}/{contentId}";
}
public string AppSettingsUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string AssetsUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string AssetsUI(NamedId<Guid> appId, string? query = null)
{
throw new NotSupportedException();
}
public string AssetSource(Guid assetId)
{
throw new NotSupportedException();
}
public string BackupsUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string ClientsUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string ContentsUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string ContentsUI(NamedId<Guid> appId, NamedId<Guid> schemaId)
{
throw new NotSupportedException();
}
public string ContributorsUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string DashboardUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string LanguagesUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string PatternsUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string PlansUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string RolesUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string RulesUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string SchemasUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string SchemaUI(NamedId<Guid> appId, NamedId<Guid> schemaId)
{
throw new NotSupportedException();
}
public string WorkflowsUI(NamedId<Guid> appId)
{
throw new NotSupportedException();
}
public string UI()
{
throw new NotSupportedException();
} }
} }
} }

7
backend/tests/Squidex.Domain.Apps.Entities.Tests/Notifications/NotificationEmailSenderTests.cs

@ -11,6 +11,7 @@ using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core;
using Squidex.Infrastructure.Email; using Squidex.Infrastructure.Email;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Shared.Identity; using Squidex.Shared.Identity;
@ -22,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Notifications
public class NotificationEmailSenderTests public class NotificationEmailSenderTests
{ {
private readonly IEmailSender emailSender = A.Fake<IEmailSender>(); private readonly IEmailSender emailSender = A.Fake<IEmailSender>();
private readonly IEmailUrlGenerator emailUrlGenerator = A.Fake<IEmailUrlGenerator>(); private readonly IUrlGenerator urlGenerator = A.Fake<IUrlGenerator>();
private readonly IUser assigner = A.Fake<IUser>(); private readonly IUser assigner = A.Fake<IUser>();
private readonly IUser user = A.Fake<IUser>(); private readonly IUser user = A.Fake<IUser>();
private readonly ISemanticLog log = A.Fake<ISemanticLog>(); private readonly ISemanticLog log = A.Fake<ISemanticLog>();
@ -45,10 +46,10 @@ namespace Squidex.Domain.Apps.Entities.Apps.Notifications
A.CallTo(() => user.Claims) A.CallTo(() => user.Claims)
.Returns(assigneeClaims); .Returns(assigneeClaims);
A.CallTo(() => emailUrlGenerator.GenerateUIUrl()) A.CallTo(() => urlGenerator.UI())
.Returns(uiUrl); .Returns(uiUrl);
sut = new NotificationEmailSender(Options.Create(texts), emailSender, emailUrlGenerator, log); sut = new NotificationEmailSender(Options.Create(texts), emailSender, urlGenerator, log);
} }
[Fact] [Fact]

Loading…
Cancel
Save