diff --git a/.drone.yml b/.drone.yml index c49d6846a..a9e87d61b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -18,12 +18,15 @@ pipeline: image: docker commands: - docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD - - docker build -t squidex/squidex:dev . + - docker build -t squidex/squidex:dev -t squidex/squidex:dev-$BUILD_NUMBER . - docker push squidex/squidex:dev + - docker push squidex/squidex:dev-$BUILD_NUMBER volumes: - /var/run/docker.sock:/var/run/docker.sock - /var/lib/docker:/var/lib/docker secrets: [ docker_username, docker_password ] + environment: + - BUILD_NUMBER=${DRONE_BUILD_NUMBER} when: event: push branch: [ master ] diff --git a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs index 80d7e23d1..46c31f642 100644 --- a/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs +++ b/extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs @@ -56,7 +56,9 @@ namespace Squidex.Extensions.Actions.Algolia { ruleDescription = $"Add entry to Algolia index: {action.IndexName}"; - ruleJob.Content = ToPayload(contentEvent); + var json = ToJson(contentEvent); + + ruleJob.Content = JObject.Parse(json); ruleJob.Content["objectID"] = contentId; } diff --git a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs index 22e9131a1..b0811ba17 100644 --- a/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs +++ b/extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs @@ -6,10 +6,10 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; @@ -32,25 +32,28 @@ namespace Squidex.Extensions.Actions.Discourse { var url = $"{action.Url.ToString().TrimEnd('/')}/posts.json?api_key={action.ApiKey}&api_username={action.ApiUsername}"; - var json = - new JObject( - new JProperty("raw", Format(action.Text, @event)), - new JProperty("title", Format(action.Title, @event))); + var json = new Dictionary + { + ["raw"] = Format(action.Text, @event), + ["title"] = Format(action.Title, @event) + }; if (action.Topic.HasValue) { - json.Add(new JProperty("topic_id", action.Topic.Value)); + json.Add("topic_id", action.Topic.Value); } if (action.Category.HasValue) { - json.Add(new JProperty("category", action.Category.Value)); + json.Add("category", action.Category.Value); } + var requestBody = ToJson(json); + var ruleJob = new DiscourseJob { RequestUrl = url, - RequestBody = json.ToString() + RequestBody = requestBody }; var description = diff --git a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs index 78655e4fd..991725964 100644 --- a/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs +++ b/extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs @@ -8,7 +8,6 @@ using System; using System.Threading.Tasks; using Elasticsearch.Net; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; @@ -60,8 +59,9 @@ namespace Squidex.Extensions.Actions.ElasticSearch { ruleDescription = $"Upsert to index: {action.IndexName}"; - ruleJob.Content = ToPayload(contentEvent); - ruleJob.Content["objectID"] = contentId; + var json = ToJson(contentEvent); + + ruleJob.Content = $"{{ \"objectId\": \"{contentId}\", {json.Substring(1)}"; } ruleJob.Username = action.Username; @@ -86,9 +86,7 @@ namespace Squidex.Extensions.Actions.ElasticSearch { if (job.Content != null) { - var doc = job.Content.ToString(); - - var response = await client.IndexAsync(job.IndexName, job.IndexType, job.ContentId, doc); + var response = await client.IndexAsync(job.IndexName, job.IndexType, job.ContentId, job.Content); return (response.Body, response.OriginalException); } @@ -116,10 +114,10 @@ namespace Squidex.Extensions.Actions.ElasticSearch public string ContentId { get; set; } + public string Content { get; set; } + public string IndexName { get; set; } public string IndexType { get; set; } - - public JObject Content { get; set; } } } diff --git a/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs b/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs index e3fe88fae..2aec919c6 100644 --- a/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs +++ b/extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs @@ -9,11 +9,10 @@ using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Infrastructure.Http; +using Squidex.Infrastructure.Json; namespace Squidex.Extensions.Actions.Medium { @@ -22,53 +21,61 @@ namespace Squidex.Extensions.Actions.Medium private const string Description = "Post to medium"; private readonly IHttpClientFactory httpClientFactory; + private readonly IJsonSerializer serializer; - public MediumActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory) + private sealed class UserResponse + { + public UserResponseData Data { get; set; } + } + + private sealed class UserResponseData + { + public string Id { get; set; } + } + + public MediumActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory, IJsonSerializer serializer) : base(formatter) { this.httpClientFactory = httpClientFactory; + + this.serializer = serializer; } protected override (string Description, MediumJob Data) CreateJob(EnrichedEvent @event, MediumAction action) { - var requestBody = - new JObject( - new JProperty("title", Format(action.Title, @event)), - new JProperty("contentFormat", action.IsHtml ? "html" : "markdown"), - new JProperty("content", Format(action.Content, @event)), - new JProperty("canonicalUrl", Format(action.CanonicalUrl, @event)), - new JProperty("tags", ParseTags(@event, action))); - - var ruleJob = new MediumJob + var ruleJob = new MediumJob { AccessToken = action.AccessToken, PublicationId = action.PublicationId }; + + var requestBody = new { - AccessToken = action.AccessToken, - PublicationId = action.PublicationId, - RequestBody = requestBody.ToString(Formatting.Indented) + title = Format(action.Title, @event), + contentFormat = action.IsHtml ? "html" : "markdown", + content = Format(action.Content, @event), + canonicalUrl = Format(action.CanonicalUrl, @event), + tags = ParseTags(@event, action) }; + ruleJob.RequestBody = ToJson(requestBody); + return (Description, ruleJob); } - private JArray ParseTags(EnrichedEvent @event, MediumAction action) + private string[] ParseTags(EnrichedEvent @event, MediumAction action) { if (string.IsNullOrWhiteSpace(action.Tags)) { return null; } - string[] tags; try { var jsonTags = Format(action.Tags, @event); - tags = JsonConvert.DeserializeObject(jsonTags); + return serializer.Deserialize(jsonTags); } catch { - tags = action.Tags.Split(','); + return action.Tags.Split(','); } - - return new JArray(tags); } protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(MediumJob job) @@ -96,9 +103,9 @@ namespace Squidex.Extensions.Actions.Medium response = await httpClient.SendAsync(meRequest); var responseString = await response.Content.ReadAsStringAsync(); - var responseJson = JToken.Parse(responseString); + var responseJson = serializer.Deserialize(responseString); - var id = responseJson["data"]["id"].ToString(); + var id = responseJson.Data?.Id; path = $"v1/users/{id}/posts"; } diff --git a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs index e8c722490..40d78e4bf 100644 --- a/extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs +++ b/extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs @@ -9,7 +9,6 @@ using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; @@ -29,12 +28,10 @@ namespace Squidex.Extensions.Actions.Prerender { var url = Format(action.Url, @event); - var request = - new JObject( - new JProperty("prerenderToken", action.Token), - new JProperty("url", url)); + var request = new { prerenderToken = action.Token, url }; + var requestBody = ToJson(request); - return ($"Recache {url}", new PrerenderJob { RequestBody = request.ToString() }); + return ($"Recache {url}", new PrerenderJob { RequestBody = requestBody }); } protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(PrerenderJob job) diff --git a/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs b/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs index aeb40f32c..3d47eaa71 100644 --- a/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs +++ b/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs @@ -9,8 +9,6 @@ using System; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Infrastructure; @@ -33,14 +31,12 @@ namespace Squidex.Extensions.Actions.Slack protected override (string Description, SlackJob Data) CreateJob(EnrichedEvent @event, SlackAction action) { - var body = - new JObject( - new JProperty("text", Format(action.Text, @event))); + var body = new { text = Format(action.Text, @event) }; var ruleJob = new SlackJob { RequestUrl = action.WebhookUrl.ToString(), - RequestBody = body.ToString(Formatting.Indented) + RequestBody = ToJson(body) }; return (Description, ruleJob); diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs index 425c14dbf..3646682a7 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs @@ -5,49 +5,60 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Immutable; +using System; +using System.Collections.Generic; using System.Diagnostics.Contracts; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; namespace Squidex.Domain.Apps.Core.Apps { - public sealed class AppClients : DictionaryWrapper + public sealed class AppClients : ArrayDictionary { public static readonly AppClients Empty = new AppClients(); private AppClients() - : base(ImmutableDictionary.Empty) { } - public AppClients(ImmutableDictionary inner) - : base(inner) + public AppClients(KeyValuePair[] items) + : base(items) { } [Pure] - public AppClients Add(string id, AppClient client) + public AppClients Revoke(string id) { Guard.NotNullOrEmpty(id, nameof(id)); - Guard.NotNull(client, nameof(client)); - return new AppClients(Inner.Add(id, client)); + return new AppClients(Without(id)); } [Pure] - public AppClients Add(string id, string secret) + public AppClients Add(string id, AppClient client) { Guard.NotNullOrEmpty(id, nameof(id)); + Guard.NotNull(client, nameof(client)); + + if (ContainsKey(id)) + { + throw new ArgumentException("Id already exists.", nameof(id)); + } - return new AppClients(Inner.Add(id, new AppClient(id, secret, Role.Editor))); + return new AppClients(With(id, client)); } [Pure] - public AppClients Revoke(string id) + public AppClients Add(string id, string secret) { Guard.NotNullOrEmpty(id, nameof(id)); - return new AppClients(Inner.Remove(id)); + if (ContainsKey(id)) + { + throw new ArgumentException("Id already exists.", nameof(id)); + } + + return new AppClients(With(id, new AppClient(id, secret, Role.Editor))); } [Pure] @@ -60,7 +71,7 @@ namespace Squidex.Domain.Apps.Core.Apps return this; } - return new AppClients(Inner.SetItem(id, client.Rename(newName))); + return new AppClients(With(id, client.Rename(newName))); } [Pure] @@ -73,7 +84,7 @@ namespace Squidex.Domain.Apps.Core.Apps return this; } - return new AppClients(Inner.SetItem(id, client.Update(role))); + return new AppClients(With(id, client.Update(role))); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs index e3bd81ab4..ee54ae6fb 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs @@ -5,23 +5,23 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Immutable; +using System.Collections.Generic; using System.Diagnostics.Contracts; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; namespace Squidex.Domain.Apps.Core.Apps { - public sealed class AppContributors : DictionaryWrapper + public sealed class AppContributors : ArrayDictionary { public static readonly AppContributors Empty = new AppContributors(); private AppContributors() - : base(ImmutableDictionary.Empty) { } - public AppContributors(ImmutableDictionary inner) - : base(inner) + public AppContributors(KeyValuePair[] items) + : base(items) { } @@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.Apps Guard.NotNullOrEmpty(contributorId, nameof(contributorId)); Guard.NotNullOrEmpty(role, nameof(role)); - return new AppContributors(Inner.SetItem(contributorId, role)); + return new AppContributors(With(contributorId, role)); } [Pure] @@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Core.Apps { Guard.NotNullOrEmpty(contributorId, nameof(contributorId)); - return new AppContributors(Inner.Remove(contributorId)); + return new AppContributors(Without(contributorId)); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs index e0ce032e3..cb9e13d3d 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs @@ -4,39 +4,45 @@ // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== + using System; -using System.Collections.Immutable; +using System.Collections.Generic; using System.Diagnostics.Contracts; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; namespace Squidex.Domain.Apps.Core.Apps { - public sealed class AppPatterns : DictionaryWrapper + public sealed class AppPatterns : ArrayDictionary { public static readonly AppPatterns Empty = new AppPatterns(); private AppPatterns() - : base(ImmutableDictionary.Empty) { } - public AppPatterns(ImmutableDictionary inner) - : base(inner) + public AppPatterns(KeyValuePair[] items) + : base(items) { } [Pure] - public AppPatterns Add(Guid id, string name, string pattern, string message) + public AppPatterns Remove(Guid id) { - var newPattern = new AppPattern(name, pattern, message); - - return new AppPatterns(Inner.Add(id, newPattern)); + return new AppPatterns(Without(id)); } [Pure] - public AppPatterns Remove(Guid id) + public AppPatterns Add(Guid id, string name, string pattern, string message) { - return new AppPatterns(Inner.Remove(id)); + var newPattern = new AppPattern(name, pattern, message); + + if (ContainsKey(id)) + { + throw new ArgumentException("Id already exists.", nameof(id)); + } + + return new AppPatterns(With(id, newPattern)); } [Pure] @@ -50,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Apps return this; } - return new AppPatterns(Inner.SetItem(id, appPattern.Update(name, pattern, message))); + return new AppPatterns(With(id, appPattern.Update(name, pattern, message))); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs index 3da12f586..ecaac12c2 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs @@ -7,9 +7,9 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; +using System.Linq; using Newtonsoft.Json; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Newtonsoft; namespace Squidex.Domain.Apps.Core.Apps.Json { @@ -31,7 +31,12 @@ namespace Squidex.Domain.Apps.Core.Apps.Json { var json = serializer.Deserialize>(reader); - return new AppClients(json.ToImmutableDictionary(x => x.Key, x => x.Value.ToClient())); + return new AppClients(json.Select(Convert).ToArray()); + } + + private static KeyValuePair Convert(KeyValuePair kvp) + { + return new KeyValuePair(kvp.Key, kvp.Value.ToClient()); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs index b3955699f..e72ecc0b0 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs @@ -7,9 +7,9 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; +using System.Linq; using Newtonsoft.Json; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Newtonsoft; namespace Squidex.Domain.Apps.Core.Apps.Json { @@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json { var json = serializer.Deserialize>(reader); - return new AppContributors(json.ToImmutableDictionary()); + return new AppContributors(json.ToArray()); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs index 20ae7248c..465029571 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs @@ -4,11 +4,12 @@ // Copyright (c) Squidex UG (haftungsbeschränkt) // All rights reserved. Licensed under the MIT license. // ========================================================================== + using System; using System.Collections.Generic; -using System.Collections.Immutable; +using System.Linq; using Newtonsoft.Json; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Newtonsoft; namespace Squidex.Domain.Apps.Core.Apps.Json { @@ -30,7 +31,12 @@ namespace Squidex.Domain.Apps.Core.Apps.Json { var json = serializer.Deserialize>(reader); - return new AppPatterns(json.ToImmutableDictionary(x => x.Key, x => x.Value.ToPattern())); + return new AppPatterns(json.Select(Convert).ToArray()); + } + + private static KeyValuePair Convert(KeyValuePair kvp) + { + return new KeyValuePair(kvp.Key, kvp.Value.ToPattern()); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs index ed396589d..a229d5593 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs @@ -7,7 +7,7 @@ using System; using Newtonsoft.Json; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Newtonsoft; namespace Squidex.Domain.Apps.Core.Apps.Json { diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs index b33285923..51b23d192 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs @@ -6,11 +6,10 @@ // ========================================================================== using Newtonsoft.Json; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Newtonsoft; using Squidex.Infrastructure.Security; using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; namespace Squidex.Domain.Apps.Core.Apps.Json @@ -33,7 +32,12 @@ namespace Squidex.Domain.Apps.Core.Apps.Json { var json = serializer.Deserialize>(reader); - return new Roles(json.ToImmutableDictionary(x => x.Key, x => new Role(x.Key, new PermissionSet(x.Value)))); + return new Roles(json.Select(Convert).ToArray()); + } + + private static KeyValuePair Convert(KeyValuePair kvp) + { + return new KeyValuePair(kvp.Key, new Role(kvp.Key, new PermissionSet(kvp.Value))); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs index 9b4374720..ac4bffde9 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Collections.Generic; using System.Linq; using Squidex.Infrastructure; @@ -13,7 +14,6 @@ namespace Squidex.Domain.Apps.Core.Apps { public sealed class LanguageConfig : IFieldPartitionItem { - private static readonly Language[] DefaultFallback = new Language[0]; private readonly Language language; private readonly Language[] languageFallbacks; @@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Apps IsOptional = isOptional; this.language = language; - this.languageFallbacks = fallback ?? DefaultFallback; + this.languageFallbacks = fallback ?? Array.Empty(); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs index 51003278f..8a9d6febe 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs @@ -8,10 +8,10 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics.Contracts; using System.Linq; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; namespace Squidex.Domain.Apps.Core.Apps { @@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Core.Apps { public static readonly LanguagesConfig English = Build(Language.EN); - private readonly ImmutableDictionary languages; + private readonly ArrayDictionary languages; private readonly LanguageConfig master; public LanguageConfig Master @@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.Apps get { return languages.Count; } } - private LanguagesConfig(ImmutableDictionary languages, LanguageConfig master, bool checkMaster = true) + private LanguagesConfig(ArrayDictionary languages, LanguageConfig master, bool checkMaster = true) { if (checkMaster) { @@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Core.Apps { Guard.NotNull(configs, nameof(configs)); - return new LanguagesConfig(configs.ToImmutableDictionary(x => x.Language), configs.FirstOrDefault()); + return new LanguagesConfig(configs.ToArrayDictionary(x => x.Language), configs.FirstOrDefault()); } public static LanguagesConfig Build(params LanguageConfig[] configs) @@ -100,7 +100,12 @@ namespace Squidex.Domain.Apps.Core.Apps { Guard.NotNull(config, nameof(config)); - return new LanguagesConfig(languages.SetItem(config.Language, config), Master?.Language == config.Language ? config : Master); + var newLanguages = + new ArrayDictionary(languages.With(config.Language, config)); + + var newMaster = Master?.Language == config.Language ? config : Master; + + return new LanguagesConfig(newLanguages, newMaster); } [Pure] @@ -114,7 +119,7 @@ namespace Squidex.Domain.Apps.Core.Apps config.Language, config.IsOptional, config.LanguageFallbacks.Except(new[] { language }))) - .ToImmutableDictionary(x => x.Language); + .ToArrayDictionary(x => x.Language); var newMaster = newLanguages.Values.FirstOrDefault(x => x.Language == Master.Language) ?? diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs index 3c0320f10..58e7c5bfa 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs @@ -6,38 +6,44 @@ // ========================================================================== using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; +using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics.Contracts; +using System.Linq; namespace Squidex.Domain.Apps.Core.Apps { - public sealed class Roles : DictionaryWrapper + public sealed class Roles : ArrayDictionary { public static readonly Roles Empty = new Roles(); private Roles() - : base(ImmutableDictionary.Empty) { } - public Roles(ImmutableDictionary inner) - : base(inner) + public Roles(KeyValuePair[] items) + : base(items) { } [Pure] - public Roles Add(string name) + public Roles Remove(string name) { - var newRole = new Role(name); - - return new Roles(Inner.Add(name, newRole)); + return new Roles(Without(name)); } [Pure] - public Roles Remove(string name) + public Roles Add(string name) { - return new Roles(Inner.Remove(name)); + var newRole = new Role(name); + + if (ContainsKey(name)) + { + throw new ArgumentException("Name already exists.", nameof(name)); + } + + return new Roles(With(name, newRole)); } [Pure] @@ -51,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.Apps return this; } - return new Roles(Inner.SetItem(name, role.Update(permissions))); + return new Roles(With(name, role.Update(permissions))); } public static Roles CreateDefaults(string app) @@ -63,7 +69,7 @@ namespace Squidex.Domain.Apps.Core.Apps [Role.Editor] = Role.CreateEditor(app), [Role.Owner] = Role.CreateOwner(app), [Role.Reader] = Role.CreateReader(app) - }.ToImmutableDictionary()); + }.ToArray()); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs index 2ebc1d054..fcfe4813e 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs @@ -9,7 +9,7 @@ using System; using System.Collections.Generic; using System.Linq; using Squidex.Infrastructure; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.Contents { @@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Contents { var resultValue = new ContentFieldData(); - foreach (var partitionValue in fieldValue.Value.Where(x => !x.Value.IsNull())) + foreach (var partitionValue in fieldValue.Value.Where(x => x.Value.Type != JsonValueType.Null)) { resultValue[partitionValue.Key] = partitionValue.Value; } diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs index 9f4cf3a75..41afa54f3 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs @@ -7,21 +7,24 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json.Linq; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.Contents { - public sealed class ContentFieldData : Dictionary, IEquatable + public sealed class ContentFieldData : Dictionary, IEquatable { - private static readonly JTokenEqualityComparer JTokenEqualityComparer = new JTokenEqualityComparer(); - public ContentFieldData() : base(StringComparer.OrdinalIgnoreCase) { } - public ContentFieldData AddValue(string key, JToken value) + public ContentFieldData AddValue(string key, object value) + { + return AddJsonValue(key, JsonValue.Create(value)); + } + + public ContentFieldData AddJsonValue(string key, IJsonValue value) { Guard.NotNullOrEmpty(key, nameof(key)); @@ -30,11 +33,6 @@ namespace Squidex.Domain.Apps.Core.Contents return this; } - public ContentFieldData AddValue(JToken value) - { - return AddValue(InvariantPartitioning.Instance.Master.Key, value); - } - public override bool Equals(object obj) { return Equals(obj as ContentFieldData); @@ -42,12 +40,12 @@ namespace Squidex.Domain.Apps.Core.Contents public bool Equals(ContentFieldData other) { - return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other, EqualityComparer.Default, JTokenEqualityComparer)); + return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other)); } public override int GetHashCode() { - return this.DictionaryHashCode(EqualityComparer.Default, JTokenEqualityComparer); + return this.DictionaryHashCode(); } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Core.Model/DictionaryWrapper{TKey,TValue}.cs b/src/Squidex.Domain.Apps.Core.Model/DictionaryWrapper{TKey,TValue}.cs deleted file mode 100644 index ee96809e0..000000000 --- a/src/Squidex.Domain.Apps.Core.Model/DictionaryWrapper{TKey,TValue}.cs +++ /dev/null @@ -1,68 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections; -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace Squidex.Domain.Apps.Core -{ - public abstract class DictionaryWrapper : IReadOnlyDictionary - { - private readonly ImmutableDictionary inner; - - public TValue this[TKey key] - { - get { return inner[key]; } - } - - public IEnumerable Keys - { - get { return inner.Keys; } - } - - public IEnumerable Values - { - get { return inner.Values; } - } - - public int Count - { - get { return inner.Count; } - } - - protected ImmutableDictionary Inner - { - get { return inner; } - } - - protected DictionaryWrapper(ImmutableDictionary inner) - { - this.inner = inner; - } - - public bool ContainsKey(TKey key) - { - return inner.ContainsKey(key); - } - - public bool TryGetValue(TKey key, out TValue value) - { - return inner.TryGetValue(key, out value); - } - - IEnumerator> IEnumerable>.GetEnumerator() - { - return inner.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return inner.GetEnumerator(); - } - } -} diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs index e9b8b77e4..7ffd04107 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs @@ -7,7 +7,7 @@ using System; using Newtonsoft.Json; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Newtonsoft; namespace Squidex.Domain.Apps.Core.Rules.Json { diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs index c83cf9626..811cd51f5 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using Newtonsoft.Json.Linq; using NodaTime; namespace Squidex.Domain.Apps.Core.Rules @@ -23,12 +22,12 @@ namespace Squidex.Domain.Apps.Core.Rules public string ActionName { get; set; } + public string ActionData { get; set; } + public string Description { get; set; } public Instant Created { get; set; } public Instant Expires { get; set; } - - public JObject ActionData { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs b/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs index e62138920..a4db365a5 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs @@ -5,15 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Immutable; using Squidex.Infrastructure; +using System.Collections.ObjectModel; namespace Squidex.Domain.Apps.Core.Rules.Triggers { [TypeName(nameof(ContentChangedTrigger))] public sealed class ContentChangedTrigger : RuleTrigger { - public ImmutableList Schemas { get; set; } + public ReadOnlyCollection Schemas { get; set; } public bool HandleAll { get; set; } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs index 7a74a39f0..1de894b81 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Infrastructure; using System; using System.Collections.Generic; using System.Diagnostics.Contracts; @@ -30,11 +31,19 @@ namespace Squidex.Domain.Apps.Core.Schemas get { return fields.ByName; } } - public ArrayField(long id, string name, Partitioning partitioning, ArrayFieldProperties properties) + public ArrayField(long id, string name, Partitioning partitioning, ArrayFieldProperties properties = null, IFieldSettings settings = null) : base(id, name, partitioning, properties) { } + public ArrayField(long id, string name, Partitioning partitioning, NestedField[] fields, ArrayFieldProperties properties = null, IFieldSettings settings = null) + : this(id, name, partitioning, properties) + { + Guard.NotNull(fields, nameof(fields)); + + this.fields = new FieldCollection(fields); + } + [Pure] public ArrayField DeleteField(long fieldId) { diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs index f3f6100d9..8a970c2af 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs @@ -27,12 +27,12 @@ namespace Squidex.Domain.Apps.Core.Schemas return visitor.Visit((IArrayField)field); } - public override RootField CreateRootField(long id, string name, Partitioning partitioning) + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { - return Fields.Array(id, name, partitioning, this); + return Fields.Array(id, name, partitioning, this, settings); } - public override NestedField CreateNestedField(long id, string name) + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) { throw new NotSupportedException(); } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs index 62b3be7c1..0cc74af29 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Immutable; using Squidex.Infrastructure; +using System.Collections.ObjectModel; namespace Squidex.Domain.Apps.Core.Schemas { @@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Core.Schemas public int? AspectHeight { get; set; } - public ImmutableList AllowedExtensions { get; set; } + public ReadOnlyCollection AllowedExtensions { get; set; } public override T Accept(IFieldPropertiesVisitor visitor) { @@ -47,14 +47,14 @@ namespace Squidex.Domain.Apps.Core.Schemas return visitor.Visit((IField)field); } - public override RootField CreateRootField(long id, string name, Partitioning partitioning) + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { - return Fields.Assets(id, name, partitioning, this); + return Fields.Assets(id, name, partitioning, this, settings); } - public override NestedField CreateNestedField(long id, string name) + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) { - return Fields.Assets(id, name, this); + return Fields.Assets(id, name, this, settings); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs index a4a0750a5..435f9c529 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs @@ -28,14 +28,14 @@ namespace Squidex.Domain.Apps.Core.Schemas return visitor.Visit((IField)field); } - public override RootField CreateRootField(long id, string name, Partitioning partitioning) + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { - return Fields.Boolean(id, name, partitioning, this); + return Fields.Boolean(id, name, partitioning, this, settings); } - public override NestedField CreateNestedField(long id, string name) + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) { - return Fields.Boolean(id, name, this); + return Fields.Boolean(id, name, this, settings); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs index efbcad12b..3e79836f0 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs @@ -33,14 +33,14 @@ namespace Squidex.Domain.Apps.Core.Schemas return visitor.Visit((IField)field); } - public override RootField CreateRootField(long id, string name, Partitioning partitioning) + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { - return Fields.DateTime(id, name, partitioning, this); + return Fields.DateTime(id, name, partitioning, this, settings); } - public override NestedField CreateNestedField(long id, string name) + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) { - return Fields.DateTime(id, name, this); + return Fields.DateTime(id, name, this, settings); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs index e1546841d..c4c6627f1 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics.Contracts; using System.Linq; using Squidex.Infrastructure; @@ -17,10 +16,13 @@ namespace Squidex.Domain.Apps.Core.Schemas public sealed class FieldCollection : Cloneable> where T : IField { public static readonly FieldCollection Empty = new FieldCollection(); + + private static readonly Dictionary EmptyById = new Dictionary(); + private static readonly Dictionary EmptyByString = new Dictionary(); - private ImmutableArray fieldsOrdered = ImmutableArray.Empty; - private ImmutableDictionary fieldsById; - private ImmutableDictionary fieldsByName; + private T[] fieldsOrdered; + private Dictionary fieldsById; + private Dictionary fieldsByName; public IReadOnlyList Ordered { @@ -35,11 +37,11 @@ namespace Squidex.Domain.Apps.Core.Schemas { if (fieldsOrdered.Length == 0) { - fieldsById = ImmutableDictionary.Empty; + fieldsById = EmptyById; } else { - fieldsById = fieldsOrdered.ToImmutableDictionary(x => x.Id); + fieldsById = fieldsOrdered.ToDictionary(x => x.Id); } } @@ -55,11 +57,11 @@ namespace Squidex.Domain.Apps.Core.Schemas { if (fieldsOrdered.Length == 0) { - fieldsByName = ImmutableDictionary.Empty; + fieldsByName = EmptyByString; } else { - fieldsByName = fieldsOrdered.ToImmutableDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); + fieldsByName = fieldsOrdered.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); } } @@ -69,13 +71,14 @@ namespace Squidex.Domain.Apps.Core.Schemas private FieldCollection() { + fieldsOrdered = Array.Empty(); } public FieldCollection(T[] fields) { Guard.NotNull(fields, nameof(fields)); - fieldsOrdered = ImmutableArray.Create(fields); + fieldsOrdered = fields; } protected override void OnCloned() @@ -94,7 +97,7 @@ namespace Squidex.Domain.Apps.Core.Schemas return Clone(clone => { - clone.fieldsOrdered = fieldsOrdered.Remove(field); + clone.fieldsOrdered = fieldsOrdered.Where(x => x.Id != fieldId).ToArray(); }); } @@ -110,7 +113,7 @@ namespace Squidex.Domain.Apps.Core.Schemas return Clone(clone => { - clone.fieldsOrdered = fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id)).ToImmutableArray(); + clone.fieldsOrdered = fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id)).ToArray(); }); } @@ -126,7 +129,7 @@ namespace Squidex.Domain.Apps.Core.Schemas return Clone(clone => { - clone.fieldsOrdered = clone.fieldsOrdered.Add(field); + clone.fieldsOrdered = clone.fieldsOrdered.Union(Enumerable.Repeat(field, 1)).ToArray(); }); } @@ -154,7 +157,7 @@ namespace Squidex.Domain.Apps.Core.Schemas return Clone(clone => { - clone.fieldsOrdered = clone.fieldsOrdered.Replace(field, typedField); + clone.fieldsOrdered = clone.fieldsOrdered.Select(x => ReferenceEquals(x, field) ? newField : x).ToArray(); }); } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs index a9c8d0421..59aabc380 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs @@ -21,8 +21,8 @@ namespace Squidex.Domain.Apps.Core.Schemas public abstract T Accept(IFieldVisitor visitor, IField field); - public abstract RootField CreateRootField(long id, string name, Partitioning partitioning); + public abstract RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null); - public abstract NestedField CreateNestedField(long id, string name); + public abstract NestedField CreateNestedField(long id, string name, IFieldSettings settings = null); } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs index 2cc7be0de..cd32ac6b7 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs @@ -42,18 +42,18 @@ namespace Squidex.Domain.Apps.Core.Schemas } } - public RootField CreateRootField(long id, string name, Partitioning partitioning, FieldProperties properties) + public RootField CreateRootField(long id, string name, Partitioning partitioning, FieldProperties properties, IFieldSettings settings = null) { CheckProperties(properties); - return properties.CreateRootField(id, name, partitioning); + return properties.CreateRootField(id, name, partitioning, settings); } - public NestedField CreateNestedField(long id, string name, FieldProperties properties) + public NestedField CreateNestedField(long id, string name, FieldProperties properties, IFieldSettings settings = null) { CheckProperties(properties); - return properties.CreateNestedField(id, name); + return properties.CreateNestedField(id, name, settings); } private void CheckProperties(FieldProperties properties) diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs index de6e49e28..5da91b6a1 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs @@ -13,117 +13,107 @@ namespace Squidex.Domain.Apps.Core.Schemas { public static RootField Array(long id, string name, Partitioning partitioning, params NestedField[] fields) { - var result = new ArrayField(id, name, partitioning, new ArrayFieldProperties()); - - if (fields != null) - { - foreach (var field in fields) - { - result = result.AddField(field); - } - } - - return result; + return new ArrayField(id, name, partitioning, fields: fields); } - public static ArrayField Array(long id, string name, Partitioning partitioning, ArrayFieldProperties properties = null) + public static ArrayField Array(long id, string name, Partitioning partitioning, ArrayFieldProperties properties = null, IFieldSettings settings = null) { - return new ArrayField(id, name, partitioning, properties ?? new ArrayFieldProperties()); + return new ArrayField(id, name, partitioning, properties, settings); } - public static RootField Assets(long id, string name, Partitioning partitioning, AssetsFieldProperties properties = null) + public static RootField Assets(long id, string name, Partitioning partitioning, AssetsFieldProperties properties = null, IFieldSettings settings = null) { - return new RootField(id, name, partitioning, properties ?? new AssetsFieldProperties()); + return new RootField(id, name, partitioning, properties, settings); } - public static RootField Boolean(long id, string name, Partitioning partitioning, BooleanFieldProperties properties = null) + public static RootField Boolean(long id, string name, Partitioning partitioning, BooleanFieldProperties properties = null, IFieldSettings settings = null) { - return new RootField(id, name, partitioning, properties ?? new BooleanFieldProperties()); + return new RootField(id, name, partitioning, properties, settings); } - public static RootField DateTime(long id, string name, Partitioning partitioning, DateTimeFieldProperties properties = null) + public static RootField DateTime(long id, string name, Partitioning partitioning, DateTimeFieldProperties properties = null, IFieldSettings settings = null) { - return new RootField(id, name, partitioning, properties ?? new DateTimeFieldProperties()); + return new RootField(id, name, partitioning, properties, settings); } - public static RootField Geolocation(long id, string name, Partitioning partitioning, GeolocationFieldProperties properties = null) + public static RootField Geolocation(long id, string name, Partitioning partitioning, GeolocationFieldProperties properties = null, IFieldSettings settings = null) { - return new RootField(id, name, partitioning, properties ?? new GeolocationFieldProperties()); + return new RootField(id, name, partitioning, properties, settings); } - public static RootField Json(long id, string name, Partitioning partitioning, JsonFieldProperties properties = null) + public static RootField Json(long id, string name, Partitioning partitioning, JsonFieldProperties properties = null, IFieldSettings settings = null) { - return new RootField(id, name, partitioning, properties ?? new JsonFieldProperties()); + return new RootField(id, name, partitioning, properties, settings); } - public static RootField Number(long id, string name, Partitioning partitioning, NumberFieldProperties properties = null) + public static RootField Number(long id, string name, Partitioning partitioning, NumberFieldProperties properties = null, IFieldSettings settings = null) { - return new RootField(id, name, partitioning, properties ?? new NumberFieldProperties()); + return new RootField(id, name, partitioning, properties, settings); } - public static RootField References(long id, string name, Partitioning partitioning, ReferencesFieldProperties properties = null) + public static RootField References(long id, string name, Partitioning partitioning, ReferencesFieldProperties properties = null, IFieldSettings settings = null) { - return new RootField(id, name, partitioning, properties ?? new ReferencesFieldProperties()); + return new RootField(id, name, partitioning, properties, settings); } - public static RootField String(long id, string name, Partitioning partitioning, StringFieldProperties properties = null) + public static RootField String(long id, string name, Partitioning partitioning, StringFieldProperties properties = null, IFieldSettings settings = null) { - return new RootField(id, name, partitioning, properties ?? new StringFieldProperties()); + return new RootField(id, name, partitioning, properties, settings); } - public static RootField Tags(long id, string name, Partitioning partitioning, TagsFieldProperties properties = null) + public static RootField Tags(long id, string name, Partitioning partitioning, TagsFieldProperties properties = null, IFieldSettings settings = null) { - return new RootField(id, name, partitioning, properties ?? new TagsFieldProperties()); + return new RootField(id, name, partitioning, properties, settings); } - public static NestedField Assets(long id, string name, AssetsFieldProperties properties = null) + public static NestedField Assets(long id, string name, AssetsFieldProperties properties = null, IFieldSettings settings = null) { - return new NestedField(id, name, properties ?? new AssetsFieldProperties()); + return new NestedField(id, name, properties, settings); } - public static NestedField Boolean(long id, string name, BooleanFieldProperties properties = null) + public static NestedField Boolean(long id, string name, BooleanFieldProperties properties = null, IFieldSettings settings = null) { - return new NestedField(id, name, properties ?? new BooleanFieldProperties()); + return new NestedField(id, name, properties, settings); } - public static NestedField DateTime(long id, string name, DateTimeFieldProperties properties = null) + public static NestedField DateTime(long id, string name, DateTimeFieldProperties properties = null, IFieldSettings settings = null) { - return new NestedField(id, name, properties ?? new DateTimeFieldProperties()); + return new NestedField(id, name, properties, settings); } - public static NestedField Geolocation(long id, string name, GeolocationFieldProperties properties = null) + public static NestedField Geolocation(long id, string name, GeolocationFieldProperties properties = null, IFieldSettings settings = null) { - return new NestedField(id, name, properties ?? new GeolocationFieldProperties()); + return new NestedField(id, name, properties, settings); } - public static NestedField Json(long id, string name, JsonFieldProperties properties = null) + public static NestedField Json(long id, string name, JsonFieldProperties properties = null, IFieldSettings settings = null) { - return new NestedField(id, name, properties ?? new JsonFieldProperties()); + return new NestedField(id, name, properties, settings); } - public static NestedField Number(long id, string name, NumberFieldProperties properties = null) + public static NestedField Number(long id, string name, NumberFieldProperties properties = null, IFieldSettings settings = null) { - return new NestedField(id, name, properties ?? new NumberFieldProperties()); + return new NestedField(id, name, properties, settings); } - public static NestedField References(long id, string name, ReferencesFieldProperties properties = null) + public static NestedField References(long id, string name, ReferencesFieldProperties properties = null, IFieldSettings settings = null) { - return new NestedField(id, name, properties ?? new ReferencesFieldProperties()); + return new NestedField(id, name, properties, settings); } - public static NestedField String(long id, string name, StringFieldProperties properties = null) + public static NestedField String(long id, string name, StringFieldProperties properties = null, IFieldSettings settings = null) { - return new NestedField(id, name, properties ?? new StringFieldProperties()); + return new NestedField(id, name, properties, settings); } - public static NestedField Tags(long id, string name, TagsFieldProperties properties = null) + public static NestedField Tags(long id, string name, TagsFieldProperties properties = null, IFieldSettings settings = null) { - return new NestedField(id, name, properties ?? new TagsFieldProperties()); + return new NestedField(id, name, properties, settings); } - public static Schema AddArray(this Schema schema, long id, string name, Partitioning partitioning, Func handler, ArrayFieldProperties properties = null) + public static Schema AddArray(this Schema schema, long id, string name, Partitioning partitioning, Func handler, ArrayFieldProperties properties = null, IFieldSettings settings = null) { - var field = Array(id, name, partitioning, properties); + var field = Array(id, name, partitioning, properties, settings); if (handler != null) { @@ -133,94 +123,94 @@ namespace Squidex.Domain.Apps.Core.Schemas return schema.AddField(field); } - public static Schema AddAssets(this Schema schema, long id, string name, Partitioning partitioning, AssetsFieldProperties properties = null) + public static Schema AddAssets(this Schema schema, long id, string name, Partitioning partitioning, AssetsFieldProperties properties = null, IFieldSettings settings = null) { - return schema.AddField(Assets(id, name, partitioning, properties)); + return schema.AddField(Assets(id, name, partitioning, properties, settings)); } - public static Schema AddBoolean(this Schema schema, long id, string name, Partitioning partitioning, BooleanFieldProperties properties = null) + public static Schema AddBoolean(this Schema schema, long id, string name, Partitioning partitioning, BooleanFieldProperties properties = null, IFieldSettings settings = null) { - return schema.AddField(Boolean(id, name, partitioning, properties)); + return schema.AddField(Boolean(id, name, partitioning, properties, settings)); } - public static Schema AddDateTime(this Schema schema, long id, string name, Partitioning partitioning, DateTimeFieldProperties properties = null) + public static Schema AddDateTime(this Schema schema, long id, string name, Partitioning partitioning, DateTimeFieldProperties properties = null, IFieldSettings settings = null) { - return schema.AddField(DateTime(id, name, partitioning, properties)); + return schema.AddField(DateTime(id, name, partitioning, properties, settings)); } - public static Schema AddGeolocation(this Schema schema, long id, string name, Partitioning partitioning, GeolocationFieldProperties properties = null) + public static Schema AddGeolocation(this Schema schema, long id, string name, Partitioning partitioning, GeolocationFieldProperties properties = null, IFieldSettings settings = null) { - return schema.AddField(Geolocation(id, name, partitioning, properties)); + return schema.AddField(Geolocation(id, name, partitioning, properties, settings)); } - public static Schema AddJson(this Schema schema, long id, string name, Partitioning partitioning, JsonFieldProperties properties = null) + public static Schema AddJson(this Schema schema, long id, string name, Partitioning partitioning, JsonFieldProperties properties = null, IFieldSettings settings = null) { - return schema.AddField(Json(id, name, partitioning, properties)); + return schema.AddField(Json(id, name, partitioning, properties, settings)); } - public static Schema AddNumber(this Schema schema, long id, string name, Partitioning partitioning, NumberFieldProperties properties = null) + public static Schema AddNumber(this Schema schema, long id, string name, Partitioning partitioning, NumberFieldProperties properties = null, IFieldSettings settings = null) { - return schema.AddField(Number(id, name, partitioning, properties)); + return schema.AddField(Number(id, name, partitioning, properties, settings)); } - public static Schema AddReferences(this Schema schema, long id, string name, Partitioning partitioning, ReferencesFieldProperties properties = null) + public static Schema AddReferences(this Schema schema, long id, string name, Partitioning partitioning, ReferencesFieldProperties properties = null, IFieldSettings settings = null) { - return schema.AddField(References(id, name, partitioning, properties)); + return schema.AddField(References(id, name, partitioning, properties, settings)); } - public static Schema AddString(this Schema schema, long id, string name, Partitioning partitioning, StringFieldProperties properties = null) + public static Schema AddString(this Schema schema, long id, string name, Partitioning partitioning, StringFieldProperties properties = null, IFieldSettings settings = null) { - return schema.AddField(String(id, name, partitioning, properties)); + return schema.AddField(String(id, name, partitioning, properties, settings)); } - public static Schema AddTags(this Schema schema, long id, string name, Partitioning partitioning, TagsFieldProperties properties = null) + public static Schema AddTags(this Schema schema, long id, string name, Partitioning partitioning, TagsFieldProperties properties = null, IFieldSettings settings = null) { - return schema.AddField(Tags(id, name, partitioning, properties)); + return schema.AddField(Tags(id, name, partitioning, properties, settings)); } - public static ArrayField AddAssets(this ArrayField field, long id, string name, AssetsFieldProperties properties = null) + public static ArrayField AddAssets(this ArrayField field, long id, string name, AssetsFieldProperties properties = null, IFieldSettings settings = null) { - return field.AddField(Assets(id, name, properties)); + return field.AddField(Assets(id, name, properties, settings)); } - public static ArrayField AddBoolean(this ArrayField field, long id, string name, BooleanFieldProperties properties = null) + public static ArrayField AddBoolean(this ArrayField field, long id, string name, BooleanFieldProperties properties = null, IFieldSettings settings = null) { - return field.AddField(Boolean(id, name, properties)); + return field.AddField(Boolean(id, name, properties, settings)); } - public static ArrayField AddDateTime(this ArrayField field, long id, string name, DateTimeFieldProperties properties = null) + public static ArrayField AddDateTime(this ArrayField field, long id, string name, DateTimeFieldProperties properties = null, IFieldSettings settings = null) { - return field.AddField(DateTime(id, name, properties)); + return field.AddField(DateTime(id, name, properties, settings)); } - public static ArrayField AddGeolocation(this ArrayField field, long id, string name, GeolocationFieldProperties properties = null) + public static ArrayField AddGeolocation(this ArrayField field, long id, string name, GeolocationFieldProperties properties = null, IFieldSettings settings = null) { - return field.AddField(Geolocation(id, name, properties)); + return field.AddField(Geolocation(id, name, properties, settings)); } - public static ArrayField AddJson(this ArrayField field, long id, string name, JsonFieldProperties properties = null) + public static ArrayField AddJson(this ArrayField field, long id, string name, JsonFieldProperties properties = null, IFieldSettings settings = null) { - return field.AddField(Json(id, name, properties)); + return field.AddField(Json(id, name, properties, settings)); } - public static ArrayField AddNumber(this ArrayField field, long id, string name, NumberFieldProperties properties = null) + public static ArrayField AddNumber(this ArrayField field, long id, string name, NumberFieldProperties properties = null, IFieldSettings settings = null) { - return field.AddField(Number(id, name, properties)); + return field.AddField(Number(id, name, properties, settings)); } - public static ArrayField AddReferences(this ArrayField field, long id, string name, ReferencesFieldProperties properties = null) + public static ArrayField AddReferences(this ArrayField field, long id, string name, ReferencesFieldProperties properties = null, IFieldSettings settings = null) { - return field.AddField(References(id, name, properties)); + return field.AddField(References(id, name, properties, settings)); } - public static ArrayField AddString(this ArrayField field, long id, string name, StringFieldProperties properties = null) + public static ArrayField AddString(this ArrayField field, long id, string name, StringFieldProperties properties = null, IFieldSettings settings = null) { - return field.AddField(String(id, name, properties)); + return field.AddField(String(id, name, properties, settings)); } - public static ArrayField AddTags(this ArrayField field, long id, string name, TagsFieldProperties properties = null) + public static ArrayField AddTags(this ArrayField field, long id, string name, TagsFieldProperties properties = null, IFieldSettings settings = null) { - return field.AddField(Tags(id, name, properties)); + return field.AddField(Tags(id, name, properties, settings)); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs index 9136b723c..fadf558fa 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs @@ -24,14 +24,14 @@ namespace Squidex.Domain.Apps.Core.Schemas return visitor.Visit((IField)field); } - public override RootField CreateRootField(long id, string name, Partitioning partitioning) + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { - return Fields.Geolocation(id, name, partitioning, this); + return Fields.Geolocation(id, name, partitioning, this, settings); } - public override NestedField CreateNestedField(long id, string name) + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) { - return Fields.Geolocation(id, name, this); + return Fields.Geolocation(id, name, this, settings); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/IField.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/IField.cs index 6cc86239d..52b1b0bd2 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/IField.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/IField.cs @@ -7,18 +7,12 @@ namespace Squidex.Domain.Apps.Core.Schemas { - public interface IField + public interface IField : IFieldSettings { long Id { get; } string Name { get; } - bool IsLocked { get; } - - bool IsDisabled { get; } - - bool IsHidden { get; } - FieldProperties RawProperties { get; } T Accept(IFieldVisitor visitor); diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldSettings.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldSettings.cs new file mode 100644 index 000000000..4caeb7827 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldSettings.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.Schemas +{ + public interface IFieldSettings + { + bool IsLocked { get; } + + bool IsDisabled { get; } + + bool IsHidden { get; } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonFieldModel.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonFieldModel.cs index 952ef87f1..d199a2497 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonFieldModel.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonFieldModel.cs @@ -5,12 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; using Newtonsoft.Json; +using Squidex.Infrastructure; +using System; +using System.Linq; +using P = Squidex.Domain.Apps.Core.Partitioning; namespace Squidex.Domain.Apps.Core.Schemas.Json { - public sealed class JsonFieldModel + public sealed class JsonFieldModel : IFieldSettings { [JsonProperty] public long Id { get; set; } @@ -34,6 +37,22 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json public FieldProperties Properties { get; set; } [JsonProperty] - public List Children { get; set; } + public JsonNestedFieldModel[] Children { get; set; } + + public RootField ToField() + { + var partitioning = P.FromString(Partitioning); + + if (Properties is ArrayFieldProperties arrayProperties) + { + var nested = Children?.ToArray(n => n.ToNestedField()) ?? Array.Empty(); + + return new ArrayField(Id, Name, partitioning, nested, arrayProperties, this); + } + else + { + return Properties.CreateRootField(Id, Name, partitioning, this); + } + } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs index 59b2035e7..c7a42baaa 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs @@ -9,7 +9,7 @@ using Newtonsoft.Json; namespace Squidex.Domain.Apps.Core.Schemas.Json { - public sealed class JsonNestedFieldModel + public sealed class JsonNestedFieldModel : IFieldSettings { [JsonProperty] public long Id { get; set; } @@ -25,5 +25,15 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json [JsonProperty] public FieldProperties Properties { get; set; } + + public bool IsLocked + { + get { return false; } + } + + public NestedField ToNestedField() + { + return Properties.CreateNestedField(Id, Name, this); + } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs index 37af1b9e6..4b0b9e917 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs @@ -5,16 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; +using System; using System.Linq; using Newtonsoft.Json; +using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Core.Schemas.Json { public sealed class JsonSchemaModel { - private static readonly RootField[] Empty = new RootField[0]; - [JsonProperty] public string Name { get; set; } @@ -25,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json public SchemaProperties Properties { get; set; } [JsonProperty] - public List Fields { get; set; } + public JsonFieldModel[] Fields { get; set; } public JsonSchemaModel() { @@ -38,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json Properties = schema.Properties; Fields = - schema.Fields.Select(x => + schema.Fields.ToArray(x => new JsonFieldModel { Id = x.Id, @@ -49,16 +48,16 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json IsDisabled = x.IsDisabled, Partitioning = x.Partitioning.Key, Properties = x.RawProperties - }).ToList(); + }); IsPublished = schema.IsPublished; } - private static List CreateChildren(IField field) + private static JsonNestedFieldModel[] CreateChildren(IField field) { if (field is ArrayField arrayField) { - return arrayField.Fields.Select(x => + return arrayField.Fields.ToArray(x => new JsonNestedFieldModel { Id = x.Id, @@ -66,68 +65,15 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json IsHidden = x.IsHidden, IsDisabled = x.IsDisabled, Properties = x.RawProperties - }).ToList(); + }); } return null; } - public Schema ToSchema(FieldRegistry registry) + public Schema ToSchema() { - var fields = Empty; - - if (Fields != null) - { - fields = new RootField[Fields.Count]; - - for (var i = 0; i < fields.Length; i++) - { - var fieldModel = Fields[i]; - - var parititonKey = new Partitioning(fieldModel.Partitioning); - - var field = registry.CreateRootField(fieldModel.Id, fieldModel.Name, parititonKey, fieldModel.Properties); - - if (field is ArrayField arrayField && fieldModel.Children?.Count > 0) - { - foreach (var nestedFieldModel in fieldModel.Children) - { - var nestedField = registry.CreateNestedField(nestedFieldModel.Id, nestedFieldModel.Name, nestedFieldModel.Properties); - - if (nestedFieldModel.IsHidden) - { - nestedField = nestedField.Hide(); - } - - if (nestedFieldModel.IsDisabled) - { - nestedField = nestedField.Disable(); - } - - arrayField = arrayField.AddField(nestedField); - } - - field = arrayField; - } - - if (fieldModel.IsDisabled) - { - field = field.Disable(); - } - - if (fieldModel.IsLocked) - { - field = field.Lock(); - } - - if (fieldModel.IsHidden) - { - field = field.Hide(); - } - - fields[i] = field; - } - } + var fields = Fields.ToArray(f => f.ToField()) ?? Array.Empty(); return new Schema(Name, fields, Properties, IsPublished); } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs index 356f5bb1d..8cd129428 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs @@ -7,22 +7,12 @@ using System; using Newtonsoft.Json; -using Squidex.Infrastructure; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Newtonsoft; namespace Squidex.Domain.Apps.Core.Schemas.Json { public sealed class SchemaConverter : JsonClassConverter { - private readonly FieldRegistry fieldRegistry; - - public SchemaConverter(FieldRegistry fieldRegistry) - { - Guard.NotNull(fieldRegistry, nameof(fieldRegistry)); - - this.fieldRegistry = fieldRegistry; - } - protected override void WriteValue(JsonWriter writer, Schema value, JsonSerializer serializer) { serializer.Serialize(writer, new JsonSchemaModel(value)); @@ -30,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json protected override Schema ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) { - return serializer.Deserialize(reader).ToSchema(fieldRegistry); + return serializer.Deserialize(reader).ToSchema(); } } } \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs index 6edb4f80b..87a5adea6 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs @@ -22,14 +22,14 @@ namespace Squidex.Domain.Apps.Core.Schemas return visitor.Visit((IField)field); } - public override RootField CreateRootField(long id, string name, Partitioning partitioning) + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { - return Fields.Json(id, name, partitioning, this); + return Fields.Json(id, name, partitioning, this, settings); } - public override NestedField CreateNestedField(long id, string name) + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) { - return Fields.Json(id, name, this); + return Fields.Json(id, name, this, settings); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs index c958951ad..9643c2ea4 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs @@ -45,13 +45,20 @@ namespace Squidex.Domain.Apps.Core.Schemas public abstract FieldProperties RawProperties { get; } - protected NestedField(long id, string name) + protected NestedField(long id, string name, IFieldSettings settings = null) { Guard.NotNullOrEmpty(name, nameof(name)); Guard.GreaterThan(id, 0, nameof(id)); fieldId = id; fieldName = name; + + if (settings != null) + { + isLocked = settings.IsLocked; + isHidden = settings.IsHidden; + isDisabled = settings.IsDisabled; + } } [Pure] diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs index 7de914a4b..808de167b 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs @@ -25,12 +25,10 @@ namespace Squidex.Domain.Apps.Core.Schemas get { return properties; } } - public NestedField(long id, string name, T properties) - : base(id, name) + public NestedField(long id, string name, T properties = null, IFieldSettings settings = null) + : base(id, name, settings) { - Guard.NotNull(properties, nameof(properties)); - - SetProperties(properties); + SetProperties(properties ?? new T()); } [Pure] diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs index 3238aff25..b97c73409 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs @@ -5,15 +5,15 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Immutable; using Squidex.Infrastructure; +using System.Collections.ObjectModel; namespace Squidex.Domain.Apps.Core.Schemas { [TypeName("NumberField")] public sealed class NumberFieldProperties : FieldProperties { - public ImmutableList AllowedValues { get; set; } + public ReadOnlyCollection AllowedValues { get; set; } public double? MaxValue { get; set; } @@ -21,6 +21,8 @@ namespace Squidex.Domain.Apps.Core.Schemas public double? DefaultValue { get; set; } + public bool IsUnique { get; set; } + public bool InlineEditable { get; set; } public NumberFieldEditor Editor { get; set; } @@ -35,14 +37,14 @@ namespace Squidex.Domain.Apps.Core.Schemas return visitor.Visit((IField)field); } - public override RootField CreateRootField(long id, string name, Partitioning partitioning) + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { - return Fields.Number(id, name, partitioning, this); + return Fields.Number(id, name, partitioning, this, settings); } - public override NestedField CreateNestedField(long id, string name) + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) { - return Fields.Number(id, name, this); + return Fields.Number(id, name, this, settings); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs index 98e4bb5ec..71cfe0cff 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs @@ -29,14 +29,14 @@ namespace Squidex.Domain.Apps.Core.Schemas return visitor.Visit((IField)field); } - public override RootField CreateRootField(long id, string name, Partitioning partitioning) + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { - return Fields.References(id, name, partitioning, this); + return Fields.References(id, name, partitioning, this, settings); } - public override NestedField CreateNestedField(long id, string name) + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) { - return Fields.References(id, name, this); + return Fields.References(id, name, this, settings); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs index 461f60365..6c21a1054 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs @@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Schemas public abstract FieldProperties RawProperties { get; } - protected RootField(long id, string name, Partitioning partitioning) + protected RootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { Guard.NotNullOrEmpty(name, nameof(name)); Guard.GreaterThan(id, 0, nameof(id)); @@ -61,6 +61,13 @@ namespace Squidex.Domain.Apps.Core.Schemas fieldName = name; this.partitioning = partitioning; + + if (settings != null) + { + isLocked = settings.IsLocked; + isHidden = settings.IsHidden; + isDisabled = settings.IsDisabled; + } } [Pure] diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs index 90165643b..cbe6716d0 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs @@ -25,12 +25,10 @@ namespace Squidex.Domain.Apps.Core.Schemas get { return properties; } } - public RootField(long id, string name, Partitioning partitioning, T properties) - : base(id, name, partitioning) + public RootField(long id, string name, Partitioning partitioning, T properties = null, IFieldSettings settings = null) + : base(id, name, partitioning, settings) { - Guard.NotNull(properties, nameof(properties)); - - SetProperties(properties); + SetProperties(properties ?? new T()); } [Pure] diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs index e9731480d..909e72b21 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs @@ -5,20 +5,22 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Immutable; using Squidex.Infrastructure; +using System.Collections.ObjectModel; namespace Squidex.Domain.Apps.Core.Schemas { [TypeName("StringField")] public sealed class StringFieldProperties : FieldProperties { - public ImmutableList AllowedValues { get; set; } + public ReadOnlyCollection AllowedValues { get; set; } public int? MinLength { get; set; } public int? MaxLength { get; set; } + public bool IsUnique { get; set; } + public bool InlineEditable { get; set; } public string DefaultValue { get; set; } @@ -39,14 +41,14 @@ namespace Squidex.Domain.Apps.Core.Schemas return visitor.Visit((IField)field); } - public override RootField CreateRootField(long id, string name, Partitioning partitioning) + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { - return Fields.String(id, name, partitioning, this); + return Fields.String(id, name, partitioning, this, settings); } - public override NestedField CreateNestedField(long id, string name) + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) { - return Fields.String(id, name, this); + return Fields.String(id, name, this, settings); } } } diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldEditor.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldEditor.cs new file mode 100644 index 000000000..0c1b15c6b --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldEditor.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.Schemas +{ + public enum TagsFieldEditor + { + Tags, + Checkboxes, + Dropdown + } +} diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs index 0c2b5df37..aeaf2074a 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs @@ -6,16 +6,21 @@ // ========================================================================== using Squidex.Infrastructure; +using System.Collections.ObjectModel; namespace Squidex.Domain.Apps.Core.Schemas { [TypeName("TagsField")] public sealed class TagsFieldProperties : FieldProperties { + public ReadOnlyCollection AllowedValues { get; set; } + public int? MinItems { get; set; } public int? MaxItems { get; set; } + public TagsFieldEditor Editor { get; set; } + public TagsFieldNormalization Normalization { get; set; } public override T Accept(IFieldPropertiesVisitor visitor) @@ -28,14 +33,14 @@ namespace Squidex.Domain.Apps.Core.Schemas return visitor.Visit((IField)field); } - public override RootField CreateRootField(long id, string name, Partitioning partitioning) + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { - return Fields.Tags(id, name, partitioning, this); + return Fields.Tags(id, name, partitioning, this, settings); } - public override NestedField CreateNestedField(long id, string name) + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) { - return Fields.Tags(id, name, this); + return Fields.Tags(id, name, this, settings); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs index de8e250a7..8a9ba47cc 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs @@ -7,13 +7,12 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ConvertContent { @@ -41,11 +40,11 @@ namespace Squidex.Domain.Apps.Core.ConvertContent return result; } - private static void AppendText(JToken value, StringBuilder stringBuilder, int maxFieldLength, string separator, bool allowObjects) + private static void AppendText(IJsonValue value, StringBuilder stringBuilder, int maxFieldLength, string separator, bool allowObjects) { - if (value?.Type == JTokenType.String) + if (value.Type == JsonValueType.String) { - var text = ((JValue)value).ToString(CultureInfo.InvariantCulture); + var text = value.ToString(); if (text.Length <= maxFieldLength) { @@ -57,18 +56,18 @@ namespace Squidex.Domain.Apps.Core.ConvertContent stringBuilder.Append(text); } } - else if (value?.Type == JTokenType.Array) + else if (value is JsonArray array) { - foreach (var item in value) + foreach (var item in array) { AppendText(item, stringBuilder, maxFieldLength, separator, true); } } - else if (value?.Type == JTokenType.Object && allowObjects) + else if (value is JsonObject obj && allowObjects) { - foreach (JProperty property in value) + foreach (var item in obj.Values) { - AppendText(property.Value, stringBuilder, maxFieldLength, separator, true); + AppendText(item, stringBuilder, maxFieldLength, separator, true); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs index 5bff83392..8a6e82895 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs @@ -7,10 +7,10 @@ using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ConvertContent { @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent languagePreferences = languagePreferences.Union(languageConfig.LanguageFallbacks).ToList(); } - var result = new Dictionary(); + var result = new Dictionary(); foreach (var fieldValue in content) { @@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent foreach (var language in languagePreferences) { - if (fieldData.TryGetValue(language, out var value) && value != null) + if (fieldData.TryGetValue(language, out var value) && value.Type != JsonValueType.Null) { result[fieldValue.Key] = value; diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs index 3ccbaf753..1052b8941 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs @@ -8,13 +8,12 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Infrastructure; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; #pragma warning disable RECS0002 // Convert anonymous method to method group @@ -44,7 +43,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent { foreach (var value in data.Values) { - if (value.IsNull()) + if (value.Type == JsonValueType.Null) { continue; } @@ -78,13 +77,13 @@ namespace Squidex.Domain.Apps.Core.ConvertContent { foreach (var partition in data) { - if (partition.Value is JArray array) + if (partition.Value is JsonArray array) { for (var i = 0; i < array.Count; i++) { var id = array[i].ToString(); - array[i] = urlGenerator.GenerateUrl(id); + array[i] = JsonValue.Create(urlGenerator.GenerateUrl(id)); } } } @@ -273,16 +272,16 @@ namespace Squidex.Domain.Apps.Core.ConvertContent foreach (var partition in data) { - if (!(partition.Value is JArray jArray)) + if (!(partition.Value is JsonArray array)) { continue; } - var newArray = new JArray(); + var newArray = JsonValue.Array(); - foreach (var item in jArray.OfType()) + foreach (var item in array.OfType()) { - var newItem = new JObject(); + var newItem = JsonValue.Object(); foreach (var kvp in item) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs index 2229afdfa..a83740e60 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs @@ -5,12 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json.Linq; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ConvertContent { public static class Value { - public static readonly JToken Unset = JValue.CreateUndefined(); + public static readonly IJsonValue Unset = JsonValue.Create("UNSET"); } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs index 343077c38..2db70c052 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs @@ -7,41 +7,41 @@ using System; using System.Text; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ConvertContent { - public delegate JToken ValueConverter(JToken value, IField field); + public delegate IJsonValue ValueConverter(IJsonValue value, IField field); public static class ValueConverters { - public static ValueConverter DecodeJson() + public static ValueConverter DecodeJson(IJsonSerializer jsonSerializer) { return (value, field) => { - if (!value.IsNull() && field is IField) + if (field is IField && value is JsonScalar s) { - var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(value.ToString())); + var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(s.Value)); - return JToken.Parse(decoded); + return jsonSerializer.Deserialize(decoded); } return value; }; } - public static ValueConverter EncodeJson() + public static ValueConverter EncodeJson(IJsonSerializer jsonSerializer) { return (value, field) => { - if (!value.IsNull() && field is IField) + if (value.Type != JsonValueType.Null && field is IField) { - var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(value.ToString())); + var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonSerializer.Serialize(value))); - return encoded; + return JsonValue.Create(encoded); } return value; @@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent { return (value, field) => { - if (value.IsNull()) + if (value.Type == JsonValueType.Null) { return value; } diff --git a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs index 2460a2c00..22f88bb85 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs @@ -5,12 +5,11 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.EnrichContent { @@ -56,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.EnrichContent var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant()); - if (field.RawProperties.IsRequired || defaultValue.IsNull()) + if (field.RawProperties.IsRequired || defaultValue.Type == JsonValueType.Null) { return; } @@ -65,13 +64,13 @@ namespace Squidex.Domain.Apps.Core.EnrichContent if (!fieldData.TryGetValue(key, out var value) || ShouldApplyDefaultValue(field, value)) { - fieldData.AddValue(key, defaultValue); + fieldData.AddJsonValue(key, defaultValue); } } - private static bool ShouldApplyDefaultValue(IField field, JToken value) + private static bool ShouldApplyDefaultValue(IField field, IJsonValue value) { - return value.IsNull() || (field is IField && value is JValue jValue && Equals(jValue.Value, string.Empty)); + return value.Type == JsonValueType.Null || (field is IField && value is JsonScalar s && string.IsNullOrEmpty(s.Value)); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs index a4f8be960..0d05fc571 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs @@ -6,14 +6,14 @@ // ========================================================================== using System.Globalization; -using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.EnrichContent { - public sealed class DefaultValueFactory : IFieldVisitor + public sealed class DefaultValueFactory : IFieldVisitor { private readonly Instant now; @@ -22,71 +22,71 @@ namespace Squidex.Domain.Apps.Core.EnrichContent this.now = now; } - public static JToken CreateDefaultValue(IField field, Instant now) + public static IJsonValue CreateDefaultValue(IField field, Instant now) { Guard.NotNull(field, nameof(field)); return field.Accept(new DefaultValueFactory(now)); } - public JToken Visit(IArrayField field) + public IJsonValue Visit(IArrayField field) { - return new JArray(); + return JsonValue.Array(); } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { - return new JArray(); + return JsonValue.Array(); } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { - return field.Properties.DefaultValue; + return JsonValue.Create(field.Properties.DefaultValue); } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { - return JValue.CreateNull(); + return JsonValue.Null; } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { - return JValue.CreateNull(); + return JsonValue.Object(); } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { - return field.Properties.DefaultValue; + return JsonValue.Create(field.Properties.DefaultValue); } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { - return new JArray(); + return JsonValue.Array(); } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { - return field.Properties.DefaultValue; + return JsonValue.Create(field.Properties.DefaultValue); } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { - return new JArray(); + return JsonValue.Array(); } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Now) { - return now.ToString(); + return JsonValue.Create(now.ToString()); } if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Today) { - return now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + return JsonValue.Create(now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); } - return field.Properties.DefaultValue?.ToString(); + return JsonValue.Create(field.Properties.DefaultValue?.ToString()); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs index 9d4fddf8d..125d529f2 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs @@ -11,7 +11,7 @@ using System.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { @@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds continue; } - foreach (var partitionValue in fieldData.Where(x => !x.Value.IsNull())) + foreach (var partitionValue in fieldData.Where(x => x.Value.Type != JsonValueType.Null)) { var ids = field.ExtractReferences(partitionValue.Value); diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs index a817aba20..a7c459a13 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs @@ -7,44 +7,44 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { - public sealed class ReferencesCleaner : IFieldVisitor + public sealed class ReferencesCleaner : IFieldVisitor { - private readonly JToken value; + private readonly IJsonValue value; private readonly ICollection oldReferences; - private ReferencesCleaner(JToken value, ICollection oldReferences) + private ReferencesCleaner(IJsonValue value, ICollection oldReferences) { this.value = value; this.oldReferences = oldReferences; } - public static JToken CleanReferences(IField field, JToken value, ICollection oldReferences) + public static IJsonValue CleanReferences(IField field, IJsonValue value, ICollection oldReferences) { return field.Accept(new ReferencesCleaner(value, oldReferences)); } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { return CleanIds(); } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { if (oldReferences.Contains(field.Properties.SchemaId)) { - return new JArray(); + return JsonValue.Array(); } return CleanIds(); } - private JToken CleanIds() + private IJsonValue CleanIds() { var ids = value.ToGuidSet(); @@ -55,45 +55,45 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds isRemoved |= ids.Remove(oldReference); } - return isRemoved ? ids.ToJToken() : value; + return isRemoved ? ids.ToJsonArray() : value; } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { return value; } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { return value; } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { return value; } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { return value; } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { return value; } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { return value; } - public JToken Visit(IField field) + public IJsonValue Visit(IField field) { return value; } - public JToken Visit(IArrayField field) + public IJsonValue Visit(IArrayField field) { return value; } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs index b7170f9aa..0a701501f 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs @@ -7,22 +7,21 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { public static class ReferencesExtensions { - public static IEnumerable ExtractReferences(this IField field, JToken value) + public static IEnumerable ExtractReferences(this IField field, IJsonValue value) { return ReferencesExtractor.ExtractReferences(field, value); } - public static JToken CleanReferences(this IField field, JToken value, ICollection oldReferences) + public static IJsonValue CleanReferences(this IField field, IJsonValue value, ICollection oldReferences) { - if (value.IsNull()) + if (value.Type == JsonValueType.Null) { return value; } @@ -30,31 +29,27 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds return ReferencesCleaner.CleanReferences(field, value, oldReferences); } - public static JToken ToJToken(this HashSet ids) + public static JsonArray ToJsonArray(this HashSet ids) { - var result = new JArray(); + var result = JsonValue.Array(); foreach (var id in ids) { - result.Add(new JValue(id)); + result.Add(JsonValue.Create(id.ToString())); } return result; } - public static HashSet ToGuidSet(this JToken value) + public static HashSet ToGuidSet(this IJsonValue value) { - if (value is JArray ids) + if (value is JsonArray array) { var result = new HashSet(); - foreach (var id in ids) + foreach (var id in array) { - if (id.Type == JTokenType.Guid) - { - result.Add((Guid)id); - } - else if (id.Type == JTokenType.String && Guid.TryParse((string)id, out var guid)) + if (id.Type == JsonValueType.String && Guid.TryParse(id.ToString(), out var guid)) { result.Add(guid); } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs index 1f661ef9c..d92b70b86 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs @@ -8,21 +8,21 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { public sealed class ReferencesExtractor : IFieldVisitor> { - private readonly JToken value; + private readonly IJsonValue value; - private ReferencesExtractor(JToken value) + private ReferencesExtractor(IJsonValue value) { this.value = value; } - public static IEnumerable ExtractReferences(IField field, JToken value) + public static IEnumerable ExtractReferences(IField field, IJsonValue value) { return field.Accept(new ReferencesExtractor(value)); } @@ -31,9 +31,9 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { var result = new List(); - if (value is JArray items) + if (value is JsonArray array) { - foreach (JObject item in items) + foreach (JsonObject item in array) { foreach (var nestedField in field.Fields) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs index e99459cc7..384a90ccb 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs @@ -8,7 +8,7 @@ using System; using System.Collections.Generic; using Squidex.Domain.Apps.Core.ConvertContent; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds return (value, field) => { - if (value.IsNull()) + if (value.Type == JsonValueType.Null) { return value; } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs index f5d03da66..0ca18fdf1 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs @@ -6,7 +6,7 @@ // ========================================================================== using System; -using Newtonsoft.Json; +using System.Runtime.Serialization; using NodaTime; using Squidex.Infrastructure; using Squidex.Shared.Users; @@ -25,10 +25,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents public long Version { get; set; } - [JsonIgnore] + [IgnoreDataMember] public abstract Guid AggregateId { get; } - [JsonIgnore] + [IgnoreDataMember] public IUser User { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs index deced9228..7ca0dc581 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs @@ -7,7 +7,6 @@ using System; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; @@ -17,8 +16,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules { Type ActionType { get; } - Task<(string Description, JObject Data)> CreateJobAsync(EnrichedEvent @event, RuleAction action); + Type DataType { get; } - Task<(string Dump, Exception Exception)> ExecuteJobAsync(JObject data); + Task<(string Description, object Data)> CreateJobAsync(EnrichedEvent @event, RuleAction action); + + Task<(string Dump, Exception Exception)> ExecuteJobAsync(object data); } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs index 0143f1956..7f6c4ed75 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs @@ -7,7 +7,6 @@ using System; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.Rules; using Squidex.Infrastructure; @@ -25,29 +24,24 @@ namespace Squidex.Domain.Apps.Core.HandleRules get { return typeof(TAction); } } - protected RuleActionHandler(RuleEventFormatter formatter) + Type IRuleActionHandler.DataType { - Guard.NotNull(formatter, nameof(formatter)); - - this.formatter = formatter; + get { return typeof(TData); } } - protected virtual string ToPayloadJson(T @event) + protected RuleActionHandler(RuleEventFormatter formatter) { - return formatter.ToPayload(@event).ToString(); - } + Guard.NotNull(formatter, nameof(formatter)); - protected virtual string ToEnvelopeJson(EnrichedEvent @event) - { - return formatter.ToEnvelope(@event).ToString(); + this.formatter = formatter; } - protected virtual JObject ToPayload(T @event) + protected virtual string ToJson(T @event) { return formatter.ToPayload(@event); } - protected virtual JObject ToEnvelope(EnrichedEvent @event) + protected virtual string ToEnvelopeJson(EnrichedEvent @event) { return formatter.ToEnvelope(@event); } @@ -62,16 +56,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules return formatter.Format(text, @event); } - async Task<(string Description, JObject Data)> IRuleActionHandler.CreateJobAsync(EnrichedEvent @event, RuleAction action) + async Task<(string Description, object Data)> IRuleActionHandler.CreateJobAsync(EnrichedEvent @event, RuleAction action) { var (description, data) = await CreateJobAsync(@event, (TAction)action); - return (description, JObject.FromObject(data)); + return (description, data); } - async Task<(string Dump, Exception Exception)> IRuleActionHandler.ExecuteJobAsync(JObject data) + async Task<(string Dump, Exception Exception)> IRuleActionHandler.ExecuteJobAsync(object data) { - var typedData = data.ToObject(); + var typedData = (TData)data; return await ExecuteJobAsync(typedData); } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs index 895a722ae..8f3bb8919 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs @@ -10,11 +10,11 @@ using System.Collections.Generic; using System.Globalization; using System.Text; using System.Text.RegularExpressions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; using Squidex.Shared.Users; namespace Squidex.Domain.Apps.Core.HandleRules @@ -27,15 +27,15 @@ namespace Squidex.Domain.Apps.Core.HandleRules private static readonly Regex ContentDataPlaceholderOld = new Regex(@"^CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled); private static readonly Regex ContentDataPlaceholderNew = new Regex(@"^\{CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}\}", RegexOptions.Compiled); private readonly List<(char[] Pattern, Func Replacer)> patterns = new List<(char[] Pattern, Func Replacer)>(); - private readonly JsonSerializer serializer; + private readonly IJsonSerializer jsonSerializer; private readonly IRuleUrlGenerator urlGenerator; - public RuleEventFormatter(JsonSerializer serializer, IRuleUrlGenerator urlGenerator) + public RuleEventFormatter(IJsonSerializer jsonSerializer, IRuleUrlGenerator urlGenerator) { - Guard.NotNull(serializer, nameof(serializer)); + Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); Guard.NotNull(urlGenerator, nameof(urlGenerator)); - this.serializer = serializer; + this.jsonSerializer = jsonSerializer; this.urlGenerator = urlGenerator; AddPattern("APP_ID", AppId); @@ -55,17 +55,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules patterns.Add((placeholder.ToCharArray(), generator)); } - public virtual JObject ToPayload(T @event) + public virtual string ToPayload(T @event) { - return JObject.FromObject(@event, serializer); + return jsonSerializer.Serialize(@event); } - public virtual JObject ToEnvelope(EnrichedEvent @event) + public virtual string ToEnvelope(EnrichedEvent @event) { - return new JObject( - new JProperty("type", @event.Name), - new JProperty("payload", ToPayload(@event)), - new JProperty("timestamp", @event.Timestamp.ToString())); + return jsonSerializer.Serialize(new { type = @event.Name, payload = @event, timestamp = @event.Timestamp }); } public string Format(string text, EnrichedEvent @event) @@ -264,14 +261,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules for (var j = 2; j < path.Length; j++) { - if (value is JObject obj && obj.TryGetValue(path[j], out value)) + if (value is JsonObject obj && obj.TryGetValue(path[j], out value)) { continue; } - if (value is JArray arr && int.TryParse(path[j], out var idx) && idx >= 0 && idx < arr.Count) + if (value is JsonArray array && int.TryParse(path[j], out var idx) && idx >= 0 && idx < array.Count) { - value = arr[idx]; + value = array[idx]; } else { @@ -279,17 +276,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules } } - if (value == null || value.Type == JTokenType.Null || value.Type == JTokenType.Undefined) + if (value == null || value.Type == JsonValueType.Null) { return Undefined; } - if (value is JValue jValue) - { - return jValue.Value.ToString(); - } - - return value.ToString(Formatting.Indented) ?? Undefined; + return value.ToString() ?? Undefined; } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs index 18329060a..d6f4fa5a3 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs @@ -11,12 +11,12 @@ using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; namespace Squidex.Domain.Apps.Core.HandleRules { @@ -26,15 +26,18 @@ namespace Squidex.Domain.Apps.Core.HandleRules private readonly Dictionary ruleTriggerHandlers; private readonly TypeNameRegistry typeNameRegistry; private readonly IEventEnricher eventEnricher; + private readonly IJsonSerializer jsonSerializer; private readonly IClock clock; public RuleService( IEnumerable ruleTriggerHandlers, IEnumerable ruleActionHandlers, IEventEnricher eventEnricher, + IJsonSerializer jsonSerializer, IClock clock, TypeNameRegistry typeNameRegistry) { + Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); Guard.NotNull(ruleTriggerHandlers, nameof(ruleTriggerHandlers)); Guard.NotNull(ruleActionHandlers, nameof(ruleActionHandlers)); Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); @@ -48,6 +51,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules this.eventEnricher = eventEnricher; + this.jsonSerializer = jsonSerializer; + this.clock = clock; } @@ -88,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules var now = clock.GetCurrentInstant(); var eventTime = - @event.Headers.Contains(CommonHeaders.Timestamp) ? + @event.Headers.ContainsKey(CommonHeaders.Timestamp) ? @event.Headers.Timestamp() : now; @@ -104,11 +109,13 @@ namespace Squidex.Domain.Apps.Core.HandleRules var actionName = typeNameRegistry.GetName(actionType); var actionData = await actionHandler.CreateJobAsync(enrichedEvent, rule.Action); + var json = jsonSerializer.Serialize(actionData.Data); + var job = new RuleJob { JobId = Guid.NewGuid(), ActionName = actionName, - ActionData = actionData.Data, + ActionData = json, AggregateId = enrichedEvent.AggregateId, AppId = appEvent.AppId.Id, Created = now, @@ -120,14 +127,18 @@ namespace Squidex.Domain.Apps.Core.HandleRules return job; } - public virtual async Task<(string Dump, RuleResult Result, TimeSpan Elapsed)> InvokeAsync(string actionName, JObject job) + public virtual async Task<(string Dump, RuleResult Result, TimeSpan Elapsed)> InvokeAsync(string actionName, string job) { try { var actionType = typeNameRegistry.GetType(actionName); var actionWatch = Stopwatch.StartNew(); - var result = await ruleActionHandlers[actionType].ExecuteJobAsync(job); + var actionHandler = ruleActionHandlers[actionType]; + + var deserialized = jsonSerializer.Deserialize(job, actionHandler.DataType); + + var result = await actionHandler.ExecuteJobAsync(deserialized); actionWatch.Stop(); diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs index 11ec3918e..ee279b40a 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs @@ -7,14 +7,14 @@ using Jint.Native; using Jint.Runtime.Descriptors; -using Newtonsoft.Json.Linq; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper { public sealed class ContentFieldProperty : PropertyDescriptor { private readonly ContentFieldObject contentField; - private JToken contentValue; + private IJsonValue contentValue; private JsValue value; private bool isChanged; @@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper } } - public JToken ContentValue + public IJsonValue ContentValue { get { return contentValue ?? (contentValue = JsonMapper.Map(value)); } } @@ -48,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper get { return isChanged; } } - public ContentFieldProperty(ContentFieldObject contentField, JToken contentValue = null) + public ContentFieldProperty(ContentFieldObject contentField, IJsonValue contentValue = null) : base(null, true, true, true) { this.contentField = contentField; diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs index 6ea3cd584..ec9d52054 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs @@ -9,61 +9,52 @@ using System; using Jint; using Jint.Native; using Jint.Native.Object; -using Newtonsoft.Json.Linq; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper { public static class JsonMapper { - public static JsValue Map(JToken value, Engine engine) + public static JsValue Map(IJsonValue value, Engine engine) { if (value == null) { return JsValue.Null; } - switch (value.Type) + switch (value) { - case JTokenType.Date: - case JTokenType.Guid: - case JTokenType.String: - case JTokenType.Uri: - case JTokenType.TimeSpan: - return new JsValue((string)value); - case JTokenType.Null: + case JsonNull n: return JsValue.Null; - case JTokenType.Undefined: - return JsValue.Undefined; - case JTokenType.Integer: - return new JsValue((long)value); - case JTokenType.Float: - return new JsValue((double)value); - case JTokenType.Boolean: - return new JsValue((bool)value); - case JTokenType.Object: - return FromObject(value, engine); - case JTokenType.Array: - { - var arr = (JArray)value; - - var target = new JsValue[arr.Count]; - - for (var i = 0; i < arr.Count; i++) - { - target[i] = Map(arr[i], engine); - } - - return engine.Array.Construct(target); - } + case JsonScalar s: + return new JsValue(s.Value); + case JsonScalar b: + return new JsValue(b.Value); + case JsonScalar b: + return new JsValue(b.Value); + case JsonObject obj: + return FromObject(obj, engine); + case JsonArray arr: + return FromArray(arr, engine); } throw new ArgumentException("Invalid json type.", nameof(value)); } - private static JsValue FromObject(JToken value, Engine engine) + private static JsValue FromArray(JsonArray arr, Engine engine) { - var obj = (JObject)value; + var target = new JsValue[arr.Count]; + for (var i = 0; i < arr.Count; i++) + { + target[i] = Map(arr[i], engine); + } + + return engine.Array.Construct(target); + } + + private static JsValue FromObject(JsonObject obj, Engine engine) + { var target = new ObjectInstance(engine); foreach (var property in obj) @@ -74,69 +65,64 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper return target; } - public static JToken Map(JsValue value) + public static IJsonValue Map(JsValue value) { - if (value == null || value.IsNull()) - { - return JValue.CreateNull(); - } - - if (value.IsUndefined()) + if (value == null || value.IsNull() || value.IsUndefined()) { - return JValue.CreateUndefined(); + return JsonValue.Null; } if (value.IsString()) { - return new JValue(value.AsString()); + return JsonValue.Create(value.AsString()); } if (value.IsBoolean()) { - return new JValue(value.AsBoolean()); + return JsonValue.Create(value.AsBoolean()); } if (value.IsNumber()) { - return new JValue(value.AsNumber()); + return JsonValue.Create(value.AsNumber()); } if (value.IsDate()) { - return new JValue(value.AsDate().ToDateTime()); + return JsonValue.Create(value.AsDate().ToString()); } if (value.IsRegExp()) { - return JValue.CreateString(value.AsRegExp().Value?.ToString()); + return JsonValue.Create(value.AsRegExp().Value?.ToString()); } if (value.IsArray()) { var arr = value.AsArray(); - var target = new JArray(); + var result = JsonValue.Array(); for (var i = 0; i < arr.GetLength(); i++) { - target.Add(Map(arr.Get(i.ToString()))); + result.Add(Map(arr.Get(i.ToString()))); } - return target; + return result; } if (value.IsObject()) { var obj = value.AsObject(); - var target = new JObject(); + var result = JsonValue.Object(); foreach (var kvp in obj.GetOwnProperties()) { - target[kvp.Key] = Map(kvp.Value.Value); + result[kvp.Key] = Map(kvp.Value.Value); } - return target; + return result; } throw new ArgumentException("Invalid json type.", nameof(value)); diff --git a/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj b/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj index d4cd7e4ec..20d04013e 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj +++ b/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj @@ -16,7 +16,6 @@ - diff --git a/src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs b/src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs index ed9cd3f05..7ecac0baf 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs @@ -8,10 +8,10 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.Tags { @@ -24,10 +24,10 @@ namespace Squidex.Domain.Apps.Core.Tags Guard.NotNull(newData, nameof(newData)); var newValues = new HashSet(); - var newArrays = new List(); + var newArrays = new List(); var oldValues = new HashSet(); - var oldArrays = new List(); + var oldArrays = new List(); GetValues(schema, newValues, newArrays, newData); @@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Tags { if (normalized.TryGetValue(array[i].ToString(), out var result)) { - array[i] = result; + array[i] = JsonValue.Create(result); } } } @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Core.Tags Guard.NotNull(schema, nameof(schema)); var tagsValues = new HashSet(); - var tagsArrays = new List(); + var tagsArrays = new List(); GetValues(schema, tagsValues, tagsArrays, datas); @@ -73,14 +73,14 @@ namespace Squidex.Domain.Apps.Core.Tags { if (denormalized.TryGetValue(array[i].ToString(), out var result)) { - array[i] = result; + array[i] = JsonValue.Create(result); } } } } } - private static void GetValues(Schema schema, HashSet values, List arrays, params NamedContentData[] datas) + private static void GetValues(Schema schema, HashSet values, List arrays, params NamedContentData[] datas) { foreach (var field in schema.Fields) { @@ -109,14 +109,12 @@ namespace Squidex.Domain.Apps.Core.Tags { foreach (var partition in fieldData) { - if (partition.Value is JArray jArray) + if (partition.Value is JsonArray array) { - foreach (var value in jArray) + foreach (var value in array) { - if (value.Type == JTokenType.Object) + if (value is JsonObject nestedObject) { - var nestedObject = (JObject)value; - if (nestedObject.TryGetValue(nestedField.Name, out var nestedValue)) { ExtractTags(nestedValue, values, arrays); @@ -133,19 +131,19 @@ namespace Squidex.Domain.Apps.Core.Tags } } - private static void ExtractTags(JToken token, ISet values, ICollection arrays) + private static void ExtractTags(IJsonValue value, ISet values, ICollection arrays) { - if (token is JArray jArray) + if (value is JsonArray array) { - foreach (var value in jArray) + foreach (var item in array) { - if (value.Type == JTokenType.String) + if (item.Type == JsonValueType.String) { - values.Add(value.ToString()); + values.Add(item.ToString()); } } - arrays.Add(jArray); + arrays.Add(array); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs index 38517df83..ab089b5d7 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs @@ -9,11 +9,11 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent.Validators; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; #pragma warning disable SA1028, IDE0004 // Code must not contain trailing whitespace @@ -22,7 +22,6 @@ namespace Squidex.Domain.Apps.Core.ValidateContent public sealed class ContentValidator { private static readonly ContentFieldData DefaultFieldData = new ContentFieldData(); - private static readonly JToken DefaultValue = JValue.CreateNull(); private readonly Schema schema; private readonly PartitionResolver partitionResolver; private readonly ValidationContext context; @@ -96,7 +95,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent var type = isLanguage ? "language" : "invariant value"; - return new ObjectValidator(fieldsValidators, isPartial, type, DefaultValue); + return new ObjectValidator(fieldsValidators, isPartial, type, JsonValue.Null); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs index a43f8a095..397ed286f 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs @@ -7,45 +7,80 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json.Linq; using NodaTime.Text; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ValidateContent { public sealed class JsonValueConverter : IFieldVisitor { - private readonly JToken value; + private readonly IJsonValue value; - private JsonValueConverter(JToken value) + private JsonValueConverter(IJsonValue value) { this.value = value; } - public static object ConvertValue(IField field, JToken json) + public static object ConvertValue(IField field, IJsonValue json) { return field.Accept(new JsonValueConverter(json)); } public object Visit(IArrayField field) { - return value.ToObject>(); + return ConvertToObjectList(); } public object Visit(IField field) { - return value.ToObject>(); + return ConvertToGuidList(); + } + + public object Visit(IField field) + { + return ConvertToGuidList(); + } + + public object Visit(IField field) + { + return ConvertToStringList(); } public object Visit(IField field) { - return (bool?)value; + if (value is JsonScalar b) + { + return b.Value; + } + + throw new InvalidCastException("Invalid json type, expected boolean."); + } + + public object Visit(IField field) + { + if (value is JsonScalar b) + { + return b.Value; + } + + throw new InvalidCastException("Invalid json type, expected number."); + } + + public object Visit(IField field) + { + if (value is JsonScalar b) + { + return b.Value; + } + + throw new InvalidCastException("Invalid json type, expected string."); } public object Visit(IField field) { - if (value.Type == JTokenType.String) + if (value.Type == JsonValueType.String) { var parseResult = InstantPattern.General.Parse(value.ToString()); @@ -62,31 +97,49 @@ namespace Squidex.Domain.Apps.Core.ValidateContent public object Visit(IField field) { - var geolocation = (JObject)value; - - foreach (var property in geolocation.Properties()) + if (value is JsonObject geolocation) { - if (!string.Equals(property.Name, "latitude", StringComparison.OrdinalIgnoreCase) && - !string.Equals(property.Name, "longitude", StringComparison.OrdinalIgnoreCase)) + foreach (var propertyName in geolocation.Keys) { - throw new InvalidCastException("Geolocation can only have latitude and longitude property."); + if (!string.Equals(propertyName, "latitude", StringComparison.OrdinalIgnoreCase) && + !string.Equals(propertyName, "longitude", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidCastException("Geolocation can only have latitude and longitude property."); + } } - } - var lat = (double)geolocation["latitude"]; - var lon = (double)geolocation["longitude"]; + if (geolocation.TryGetValue("latitude", out var latValue) && latValue is JsonScalar latNumber) + { + var lat = latNumber.Value; - if (!lat.IsBetween(-90, 90)) - { - throw new InvalidCastException("Latitude must be between -90 and 90."); - } + if (!lat.IsBetween(-90, 90)) + { + throw new InvalidCastException("Latitude must be between -90 and 90."); + } + } + else + { + throw new InvalidCastException("Invalid json type, expected latitude/longitude object."); + } - if (!lon.IsBetween(-180, 180)) - { - throw new InvalidCastException("Longitude must be between -180 and 180."); + if (geolocation.TryGetValue("longitude", out var lonValue) && lonValue is JsonScalar lonNumber) + { + var lon = lonNumber.Value; + + if (!lon.IsBetween(-180, 180)) + { + throw new InvalidCastException("Longitude must be between -180 and 180."); + } + } + else + { + throw new InvalidCastException("Invalid json type, expected latitude/longitude object."); + } + + return value; } - return value; + throw new InvalidCastException("Invalid json type, expected latitude/longitude object."); } public object Visit(IField field) @@ -94,24 +147,76 @@ namespace Squidex.Domain.Apps.Core.ValidateContent return value; } - public object Visit(IField field) + private object ConvertToGuidList() { - return (double?)value; - } + if (value is JsonArray array) + { + var result = new List(); - public object Visit(IField field) - { - return value.ToObject>(); + foreach (var item in array) + { + if (item is JsonScalar s && Guid.TryParse(s.Value, out var guid)) + { + result.Add(guid); + } + else + { + throw new InvalidCastException("Invalid json type, expected array of guid strings."); + } + } + + return result; + } + + throw new InvalidCastException("Invalid json type, expected array of guid strings."); } - public object Visit(IField field) + private object ConvertToStringList() { - return value.ToString(); + if (value is JsonArray array) + { + var result = new List(); + + foreach (var item in array) + { + if (item is JsonScalar s) + { + result.Add(s.Value); + } + else + { + throw new InvalidCastException("Invalid json type, expected array of strings."); + } + } + + return result; + } + + throw new InvalidCastException("Invalid json type, expected array of strings."); } - public object Visit(IField field) + private object ConvertToObjectList() { - return value.ToObject>(); + if (value is JsonArray array) + { + var result = new List(); + + foreach (var item in array) + { + if (item is JsonObject obj) + { + result.Add(obj); + } + else + { + throw new InvalidCastException("Invalid json type, expected array of objects."); + } + } + + return result; + } + + throw new InvalidCastException("Invalid json type, expected array of objects."); } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs index 5db995c34..bc12a33dd 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs @@ -10,13 +10,20 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; using Squidex.Infrastructure; +using Squidex.Infrastructure.Queries; namespace Squidex.Domain.Apps.Core.ValidateContent { + public delegate Task> CheckContents(Guid schemaId, FilterNode filter); + + public delegate Task> CheckAssets(IEnumerable ids); + public sealed class ValidationContext { - private readonly Func, Guid, Task>> checkContent; - private readonly Func, Task>> checkAsset; + private readonly Guid contentId; + private readonly Guid schemaId; + private readonly CheckContents checkContent; + private readonly CheckAssets checkAsset; private readonly ImmutableQueue propertyPath; public ImmutableQueue Path @@ -24,18 +31,32 @@ namespace Squidex.Domain.Apps.Core.ValidateContent get { return propertyPath; } } + public Guid ContentId + { + get { return contentId; } + } + + public Guid SchemaId + { + get { return schemaId; } + } + public bool IsOptional { get; } public ValidationContext( - Func, Guid, Task>> checkContent, - Func, Task>> checkAsset) - : this(checkContent, checkAsset, ImmutableQueue.Empty, false) + Guid contentId, + Guid schemaId, + CheckContents checkContent, + CheckAssets checkAsset) + : this(contentId, schemaId, checkContent, checkAsset, ImmutableQueue.Empty, false) { } private ValidationContext( - Func, Guid, Task>> checkContent, - Func, Task>> checkAsset, + Guid contentId, + Guid schemaId, + CheckContents checkContent, + CheckAssets checkAsset, ImmutableQueue propertyPath, bool isOptional) { @@ -46,23 +67,26 @@ namespace Squidex.Domain.Apps.Core.ValidateContent this.checkContent = checkContent; this.checkAsset = checkAsset; + this.contentId = contentId; + + this.schemaId = schemaId; IsOptional = isOptional; } public ValidationContext Optional(bool isOptional) { - return isOptional == IsOptional ? this : new ValidationContext(checkContent, checkAsset, propertyPath, isOptional); + return isOptional == IsOptional ? this : new ValidationContext(contentId, schemaId, checkContent, checkAsset, propertyPath, isOptional); } public ValidationContext Nested(string property) { - return new ValidationContext(checkContent, checkAsset, propertyPath.Enqueue(property), IsOptional); + return new ValidationContext(contentId, schemaId, checkContent, checkAsset, propertyPath.Enqueue(property), IsOptional); } - public Task> GetInvalidContentIdsAsync(IEnumerable contentIds, Guid schemaId) + public Task> GetContentIdsAsync(Guid schemaId, FilterNode filter) { - return checkContent(contentIds, schemaId); + return checkContent(schemaId, filter); } public Task> GetAssetInfosAsync(IEnumerable assetId) diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs index 9b1d6129d..cc1fcdfca 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Squidex.Infrastructure; @@ -14,9 +15,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public sealed class AllowedValuesValidator : IValidator { - private readonly T[] allowedValues; + private readonly IEnumerable allowedValues; public AllowedValuesValidator(params T[] allowedValues) + : this((IEnumerable)allowedValues) + { + } + + public AllowedValuesValidator(IEnumerable allowedValues) { Guard.NotNull(allowedValues, nameof(allowedValues)); diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs index 97471857e..40bf1a89d 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs @@ -7,9 +7,8 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; -using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -30,9 +29,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { object typedValue = null; - if (value is JToken jToken) + if (value is IJsonValue jsonValue) { - typedValue = jToken.IsNull() ? null : JsonValueConverter.ConvertValue(field, jToken); + typedValue = jsonValue.Type == JsonValueType.Null ? null : JsonValueConverter.ConvertValue(field, jsonValue); } var tasks = new List(); diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs index 6dd8a9c28..429962996 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs @@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators public async Task ValidateAsync(object value, ValidationContext context, AddError addError) { - if (value is IDictionary values) + if (value is IReadOnlyDictionary values) { foreach (var fieldData in values) { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs index 9fd97bc02..fa388d365 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs @@ -7,12 +7,16 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Squidex.Infrastructure.Queries; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public sealed class ReferencesValidator : IValidator { + private static readonly IReadOnlyList Path = new List { "Id" }; + private readonly Guid schemaId; public ReferencesValidator(Guid schemaId) @@ -24,11 +28,16 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { if (value is ICollection contentIds) { - var invalidIds = await context.GetInvalidContentIdsAsync(contentIds, schemaId); + var filter = new FilterComparison(Path, FilterOperator.In, new FilterValue(contentIds.ToList())); + + var foundIds = await context.GetContentIdsAsync(schemaId, filter); - foreach (var invalidId in invalidIds) + foreach (var id in contentIds) { - addError(context.Path, $"Contains invalid reference '{invalidId}'."); + if (!foundIds.Contains(id)) + { + addError(context.Path, $"Contains invalid reference '{id}'."); + } } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs new file mode 100644 index 000000000..3c2b8f14a --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs @@ -0,0 +1,51 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Squidex.Infrastructure.Queries; + +namespace Squidex.Domain.Apps.Core.ValidateContent.Validators +{ + public sealed class UniqueValidator : IValidator + { + public async Task ValidateAsync(object value, ValidationContext context, AddError addError) + { + var count = context.Path.Count(); + + if (value != null && (count == 0 || (count == 2 && context.Path.Last() == InvariantPartitioning.Instance.Master.Key))) + { + FilterNode filter = null; + + if (value is string s) + { + filter = new FilterComparison(Path(context), FilterOperator.Equals, new FilterValue(s)); + } + else if (value is double d) + { + filter = new FilterComparison(Path(context), FilterOperator.Equals, new FilterValue(d)); + } + + if (filter != null) + { + var found = await context.GetContentIdsAsync(context.SchemaId, filter); + + if (found.Any(x => x != context.ContentId)) + { + addError(context.Path, "Another content with the same value exists."); + } + } + } + } + + private static List Path(ValidationContext context) + { + return Enumerable.Repeat("Data", 1).Union(context.Path).ToList(); + } + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs index eba15cb29..afd76321d 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs @@ -8,11 +8,11 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent.Validators; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ValidateContent { @@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent nestedSchema[nestedField.Name] = (false, new FieldValidator(nestedField.Accept(this).ToArray(), nestedField)); } - yield return new CollectionItemValidator(new ObjectValidator(nestedSchema, false, "field", JValue.CreateNull())); + yield return new CollectionItemValidator(new ObjectValidator(nestedSchema, false, "field", JsonValue.Null)); } public IEnumerable Visit(IField field) @@ -109,7 +109,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent if (field.Properties.AllowedValues != null) { - yield return new AllowedValuesValidator(field.Properties.AllowedValues.ToArray()); + yield return new AllowedValuesValidator(field.Properties.AllowedValues); + } + + if (field.Properties.IsUnique) + { + yield return new UniqueValidator(); } } @@ -145,7 +150,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent if (field.Properties.AllowedValues != null) { - yield return new AllowedValuesValidator(field.Properties.AllowedValues.ToArray()); + yield return new AllowedValuesValidator(field.Properties.AllowedValues); + } + + if (field.Properties.IsUnique) + { + yield return new UniqueValidator(); } } @@ -156,6 +166,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent yield return new CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems); } + if (field.Properties.AllowedValues != null) + { + yield return new CollectionItemValidator(new AllowedValuesValidator(field.Properties.AllowedValues)); + } + yield return new CollectionItemValidator(new RequiredStringValidator()); } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs index c3fc170a7..c677386a6 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs @@ -12,6 +12,7 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { @@ -22,24 +23,24 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents return data.GetReferencedIds(schema).ToList(); } - public static NamedContentData FromMongoModel(this IdContentData result, Schema schema, List deletedIds) + public static NamedContentData FromMongoModel(this IdContentData result, Schema schema, List deletedIds, IJsonSerializer serializer) { return result.ConvertId2Name(schema, FieldConverters.ForValues( - ValueConverters.DecodeJson(), + ValueConverters.DecodeJson(serializer), ValueReferencesConverter.CleanReferences(deletedIds)), FieldConverters.ForNestedId2Name( - ValueConverters.DecodeJson(), + ValueConverters.DecodeJson(serializer), ValueReferencesConverter.CleanReferences(deletedIds))); } - public static IdContentData ToMongoModel(this NamedContentData result, Schema schema) + public static IdContentData ToMongoModel(this NamedContentData result, Schema schema, IJsonSerializer serializer) { return result.ConvertName2Id(schema, FieldConverters.ForValues( - ValueConverters.EncodeJson()), + ValueConverters.EncodeJson(serializer)), FieldConverters.ForNestedName2Id( - ValueConverters.EncodeJson())); + ValueConverters.EncodeJson(serializer))); } } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs index c9eec5756..c9cb13fed 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs @@ -17,6 +17,7 @@ using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.Queries; @@ -26,10 +27,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { private readonly string collectionName; - public MongoContentCollection(IMongoDatabase database, string collectionName) + protected IJsonSerializer Serializer { get; } + + public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer, string collectionName) : base(database) { this.collectionName = collectionName; + + Serializer = serializer; } protected override async Task SetupCollectionAsync(IMongoCollection collection, CancellationToken ct = default(CancellationToken)) @@ -49,7 +54,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { query = query.AdjustToModel(schema.SchemaDef, useDraft); - var filter = FindExtensions.BuildQuery(query, schema.Id, status); + var filter = query.ToFilter(schema.Id, status); var contentCount = Collection.Find(filter).CountDocumentsAsync(); var contentItems = @@ -64,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents foreach (var entity in contentItems.Result) { - entity.ParseData(schema.SchemaDef); + entity.ParseData(schema.SchemaDef, Serializer); } return ResultList.Create(contentCount.Result, contentItems.Result); @@ -96,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents foreach (var entity in contentItems.Result) { - entity.ParseData(schema.SchemaDef); + entity.ParseData(schema.SchemaDef, Serializer); } return ResultList.Create(contentCount.Result, contentItems.Result); diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs index 2c80690f7..7e0c8a162 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs @@ -16,9 +16,12 @@ using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.State; +using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.MongoDb; +using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.States; @@ -26,8 +29,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { internal sealed class MongoContentDraftCollection : MongoContentCollection { - public MongoContentDraftCollection(IMongoDatabase database) - : base(database, "State_Content_Draft") + public MongoContentDraftCollection(IMongoDatabase database, IJsonSerializer serializer) + : base(database, serializer, "State_Content_Draft") { } @@ -52,13 +55,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents await base.SetupCollectionAsync(collection, ct); } - public async Task> QueryNotFoundAsync(Guid appId, Guid schemaId, IList ids) + public async Task> QueryIdsAsync(Guid appId, ISchemaEntity schema, FilterNode filterNode) { + var filter = filterNode.AdjustToModel(schema.SchemaDef, true).ToFilter(schema.Id); + var contentEntities = - await Collection.Find(x => x.IndexedSchemaId == schemaId && ids.Contains(x.Id) && x.IsDeleted != true).Only(x => x.Id) + await Collection.Find(filter).Only(x => x.Id) .ToListAsync(); - return ids.Except(contentEntities.Select(x => Guid.Parse(x["_id"].AsString))).ToList(); + return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList(); } public async Task> QueryIdsAsync(Guid appId) @@ -88,7 +93,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id && x.IsDeleted != true).Not(x => x.DataText) .FirstOrDefaultAsync(); - contentEntity?.ParseData(schema.SchemaDef); + contentEntity?.ParseData(schema.SchemaDef, Serializer); return contentEntity; } @@ -103,7 +108,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId); - contentEntity.ParseData(schema.SchemaDef); + contentEntity.ParseData(schema.SchemaDef, Serializer); return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version); } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs index b758c7919..a2346db31 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs @@ -14,6 +14,7 @@ using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.MongoDb; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents @@ -24,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents private NamedContentData dataDraft; [BsonId] - [BsonElement] + [BsonElement("_id")] [BsonRepresentation(BsonType.String)] public Guid Id { get; set; } @@ -124,13 +125,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents get { return dataDraft; } } - public void ParseData(Schema schema) + public void ParseData(Schema schema, IJsonSerializer serializer) { - data = DataByIds.FromMongoModel(schema, ReferencedIdsDeleted); + data = DataByIds.FromMongoModel(schema, ReferencedIdsDeleted, serializer); if (DataDraftByIds != null) { - dataDraft = DataDraftByIds.FromMongoModel(schema, ReferencedIdsDeleted); + dataDraft = DataDraftByIds.FromMongoModel(schema, ReferencedIdsDeleted, serializer); } } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs index 97ca68a3d..139869959 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs @@ -13,14 +13,15 @@ using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.MongoDb; namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { internal sealed class MongoContentPublishedCollection : MongoContentCollection { - public MongoContentPublishedCollection(IMongoDatabase database) - : base(database, "State_Content_Published") + public MongoContentPublishedCollection(IMongoDatabase database, IJsonSerializer serializer) + : base(database, serializer, "State_Content_Published") { } @@ -42,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id).Not(x => x.DataText) .FirstOrDefaultAsync(); - contentEntity?.ParseData(schema.SchemaDef); + contentEntity?.ParseData(schema.SchemaDef, Serializer); return contentEntity; } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs index 71c81e3aa..30a0c3de5 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs @@ -17,6 +17,7 @@ using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Queries; @@ -26,17 +27,21 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents { private readonly IMongoDatabase database; private readonly IAppProvider appProvider; + private readonly IJsonSerializer serializer; private readonly MongoContentDraftCollection contentsDraft; private readonly MongoContentPublishedCollection contentsPublished; - public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider) + public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, IJsonSerializer serializer) { Guard.NotNull(appProvider, nameof(appProvider)); + Guard.NotNull(serializer, nameof(serializer)); this.appProvider = appProvider; - contentsDraft = new MongoContentDraftCollection(database); - contentsPublished = new MongoContentPublishedCollection(database); + this.serializer = serializer; + + contentsDraft = new MongoContentDraftCollection(database, serializer); + contentsPublished = new MongoContentPublishedCollection(database, serializer); this.database = database; } @@ -91,11 +96,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents } } - public async Task> QueryNotFoundAsync(Guid appId, Guid schemaId, IList ids) + public async Task> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode filterNode) { using (Profiler.TraceMethod()) { - return await contentsDraft.QueryNotFoundAsync(appId, schemaId, ids); + return await contentsDraft.QueryIdsAsync(appId, await appProvider.GetSchemaAsync(appId, schemaId), filterNode); } } diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs index cd6700635..32f38e982 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs @@ -38,12 +38,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents var schema = await GetSchemaAsync(value.AppId.Id, value.SchemaId.Id); - var idData = value.Data.ToMongoModel(schema.SchemaDef); + var idData = value.Data.ToMongoModel(schema.SchemaDef, serializer); var idDraftData = idData; if (!ReferenceEquals(value.Data, value.DataDraft)) { - idDraftData = value.DataDraft?.ToMongoModel(schema.SchemaDef); + idDraftData = value.DataDraft?.ToMongoModel(schema.SchemaDef, serializer); } var content = SimpleMapper.Map(value, new MongoContentEntity diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs new file mode 100644 index 000000000..396971e84 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs @@ -0,0 +1,50 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using NodaTime; +using Squidex.Infrastructure.Queries; + +namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors +{ + internal sealed class AdaptionVisitor : TransformVisitor + { + private readonly Func, IReadOnlyList> pathConverter; + + public AdaptionVisitor(Func, IReadOnlyList> pathConverter) + { + this.pathConverter = pathConverter; + } + + public override FilterNode Visit(FilterComparison nodeIn) + { + FilterComparison result; + + var value = nodeIn.Rhs.Value; + + if (value is Instant && + !string.Equals(nodeIn.Lhs[0], "mt", StringComparison.OrdinalIgnoreCase) && + !string.Equals(nodeIn.Lhs[0], "ct", StringComparison.OrdinalIgnoreCase)) + { + result = new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, new FilterValue(value.ToString())); + } + else + { + result = new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, nodeIn.Rhs); + } + + if (result.Lhs.Count == 1 && result.Lhs[0] == "_id" && result.Rhs.Value is List guidList) + { + result = new FilterComparison(nodeIn.Lhs, nodeIn.Operator, new FilterValue(guidList.Select(x => x.ToString()).ToList())); + } + + return result; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs index 8fe06f208..0f57dacfa 100644 --- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs +++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs @@ -11,7 +11,6 @@ using System.Linq; using System.Reflection; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; -using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.GenerateEdmSchema; using Squidex.Domain.Apps.Core.Schemas; @@ -28,33 +27,30 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors typeof(MongoContentEntity).GetProperties() .ToDictionary(x => x.Name, x => x.GetCustomAttribute()?.ElementName ?? x.Name, StringComparer.OrdinalIgnoreCase); - private sealed class AdaptionVisitor : TransformVisitor + public static Query AdjustToModel(this Query query, Schema schema, bool useDraft) { - private readonly Func, IReadOnlyList> pathConverter; + var pathConverter = PathConverter(schema, useDraft); - public AdaptionVisitor(Func, IReadOnlyList> pathConverter) + if (query.Filter != null) { - this.pathConverter = pathConverter; + query.Filter = query.Filter.Accept(new AdaptionVisitor(pathConverter)); } - public override FilterNode Visit(FilterComparison nodeIn) - { - var value = nodeIn.Rhs.Value; + query.Sort = query.Sort.Select(x => new SortNode(pathConverter(x.Path), x.SortOrder)).ToList(); - if (value is Instant && - !string.Equals(nodeIn.Lhs[0], "mt", StringComparison.OrdinalIgnoreCase) && - !string.Equals(nodeIn.Lhs[0], "ct", StringComparison.OrdinalIgnoreCase)) - { - return new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, new FilterValue(value.ToString())); - } + return query; + } - return new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, nodeIn.Rhs); - } + public static FilterNode AdjustToModel(this FilterNode filterNode, Schema schema, bool useDraft) + { + var pathConverter = PathConverter(schema, useDraft); + + return filterNode.Accept(new AdaptionVisitor(pathConverter)); } - public static Query AdjustToModel(this Query query, Schema schema, bool useDraft) + private static Func, IReadOnlyList> PathConverter(Schema schema, bool useDraft) { - var pathConverter = new Func, IReadOnlyList>(propertyNames => + return new Func, IReadOnlyList>(propertyNames => { var result = new List(propertyNames); @@ -96,15 +92,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors return result; }); - - if (query.Filter != null) - { - query.Filter = query.Filter.Accept(new AdaptionVisitor(pathConverter)); - } - - query.Sort = query.Sort.Select(x => new SortNode(pathConverter(x.Path), x.SortOrder)).ToList(); - - return query; } public static IFindFluent ContentSort(this IFindFluent cursor, Query query) @@ -122,16 +109,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors return cursor.Skip(query); } - public static FilterDefinition BuildQuery(Query query, Guid schemaId, Status[] status) + public static FilterDefinition ToFilter(this Query query, Guid schemaId, Status[] status) { var filters = new List> { - Filter.Eq(x => x.IndexedSchemaId, schemaId) + Filter.Eq(x => x.IndexedSchemaId, schemaId), + Filter.Ne(x => x.IsDeleted, true) }; if (status != null) { - filters.Add(Filter.Ne(x => x.IsDeleted, true)); filters.Add(Filter.In(x => x.Status, status)); } @@ -149,14 +136,19 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors } } - if (filters.Count == 1) - { - return filters[0]; - } - else + return Filter.And(filters); + } + + public static FilterDefinition ToFilter(this FilterNode filterNode, Guid schemaId) + { + var filters = new List> { - return Filter.And(filters); - } + Filter.Eq(x => x.IndexedSchemaId, schemaId), + Filter.Ne(x => x.IsDeleted, true), + filterNode.BuildFilter() + }; + + return Filter.And(filters); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs index ea1e17408..e19911379 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs @@ -8,8 +8,8 @@ using System; using System.Linq; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.States; @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Apps [CollectionName("UISettings")] public sealed class State { - public JObject Settings { get; set; } = new JObject(); + public JsonObject Settings { get; set; } = JsonValue.Object(); } public AppUISettingsGrain(IStore store) @@ -41,19 +41,19 @@ namespace Squidex.Domain.Apps.Entities.Apps return persistence.ReadAsync(); } - public Task> GetAsync() + public Task> GetAsync() { return Task.FromResult(state.Settings.AsJ()); } - public Task SetAsync(J settings) + public Task SetAsync(J settings) { state.Settings = settings; return persistence.WriteSnapshotAsync(state); } - public Task SetAsync(string path, J value) + public Task SetAsync(string path, J value) { var container = GetContainer(path, out var key); @@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Apps throw new InvalidOperationException("Path does not lead to an object."); } - container[key] = value; + container[key] = value.Value; return persistence.WriteSnapshotAsync(state); } @@ -79,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.Apps return persistence.WriteSnapshotAsync(state); } - private JObject GetContainer(string path, out string key) + private JsonObject GetContainer(string path, out string key) { Guard.NotNullOrEmpty(path, nameof(path)); @@ -95,12 +95,12 @@ namespace Squidex.Domain.Apps.Entities.Apps { if (!current.TryGetValue(segment, out var temp)) { - temp = new JObject(); + temp = JsonValue.Object(); current[segment] = temp; } - if (temp is JObject next) + if (temp is JsonObject next) { current = next; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs b/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs index 1cc342591..6d7b8e3d6 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Orleans; using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Backup; @@ -16,6 +15,8 @@ using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Orleans; using Squidex.Shared.Users; @@ -27,6 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Apps private const string SettingsFile = "Settings.json"; private readonly IGrainFactory grainFactory; private readonly IUserResolver userResolver; + private readonly IJsonSerializer serializer; private readonly IAppsByNameIndex appsByNameIndex; private readonly HashSet contributors = new HashSet(); private Dictionary usersWithEmail = new Dictionary(); @@ -36,13 +38,14 @@ namespace Squidex.Domain.Apps.Entities.Apps public override string Name { get; } = "Apps"; - public BackupApps(IGrainFactory grainFactory, IUserResolver userResolver) + public BackupApps(IGrainFactory grainFactory, IUserResolver userResolver, IJsonSerializer serializer) { Guard.NotNull(grainFactory, nameof(grainFactory)); + Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(userResolver, nameof(userResolver)); this.grainFactory = grainFactory; - + this.serializer = serializer; this.userResolver = userResolver; appsByNameIndex = grainFactory.GetGrain(SingleGrain.Id); @@ -162,14 +165,14 @@ namespace Squidex.Domain.Apps.Entities.Apps private async Task ReadUsersAsync(BackupReader reader) { - var json = await reader.ReadJsonAttachmentAsync(UsersFile); + var json = await reader.ReadJsonAttachmentAsync>(UsersFile); - usersWithEmail = json.ToObject>(); + usersWithEmail = json; } private async Task WriteUsersAsync(BackupWriter writer) { - var json = JObject.FromObject(usersWithEmail); + var json = usersWithEmail; await writer.WriteJsonAsync(UsersFile, json); } @@ -183,9 +186,9 @@ namespace Squidex.Domain.Apps.Entities.Apps private async Task ReadSettingsAsync(BackupReader reader, Guid appId) { - var json = await reader.ReadJsonAttachmentAsync(SettingsFile); + var json = await reader.ReadJsonAttachmentAsync(SettingsFile); - await grainFactory.GetGrain(appId).SetAsync((JObject)json); + await grainFactory.GetGrain(appId).SetAsync(json); } public override async Task CompleteRestoreAsync(Guid appId, BackupReader reader) diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs b/src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs index 279f14790..0e5f68fd9 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Orleans; @@ -19,6 +20,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics { private readonly IAppsByNameIndex index; + public IEnumerable Scopes + { + get { yield return HealthCheckScopes.Cluster; } + } + public OrleansAppsHealthCheck(IGrainFactory grainFactory) { Guard.NotNull(grainFactory, nameof(grainFactory)); diff --git a/src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs index 38fde5c74..69583ff25 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs @@ -6,19 +6,19 @@ // ========================================================================== using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Orleans; +using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Orleans; namespace Squidex.Domain.Apps.Entities.Apps { public interface IAppUISettingsGrain : IGrainWithGuidKey { - Task> GetAsync(); + Task> GetAsync(); - Task SetAsync(string path, J value); + Task SetAsync(string path, J value); - Task SetAsync(J settings); + Task SetAsync(J settings); Task RemoveAsync(string path); } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs index 345a5b32c..36d8ce960 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json; +using System.Runtime.Serialization; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Apps; @@ -19,28 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Apps.State [CollectionName("Apps")] public class AppState : DomainObjectState, IAppEntity { - [JsonProperty] + [DataMember] public string Name { get; set; } - [JsonProperty] + [DataMember] public Roles Roles { get; set; } = Roles.Empty; - [JsonProperty] + [DataMember] public AppPlan Plan { get; set; } - [JsonProperty] + [DataMember] public AppClients Clients { get; set; } = AppClients.Empty; - [JsonProperty] + [DataMember] public AppPatterns Patterns { get; set; } = AppPatterns.Empty; - [JsonProperty] + [DataMember] public AppContributors Contributors { get; set; } = AppContributors.Empty; - [JsonProperty] + [DataMember] public LanguagesConfig LanguagesConfig { get; set; } = LanguagesConfig.English; - [JsonProperty] + [DataMember] public bool IsArchived { get; set; } protected void On(AppCreated @event) diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs index 4a798f72b..0fd4b857b 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs @@ -5,9 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Immutable; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Infrastructure.Collections; namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders { @@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders public StringFieldBuilder AsDropDown(params string[] values) { - Properties().AllowedValues = ImmutableList.Create(values); + Properties().AllowedValues = ReadOnlyCollection.Create(values); Properties().Editor = StringFieldEditor.Dropdown; return this; diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs index 0199126dc..eb6eb6168 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs @@ -34,7 +34,9 @@ namespace Squidex.Domain.Apps.Entities.Assets public async Task GetTotalSizeAsync(Guid appId) { - var entries = await usageStore.QueryAsync(appId.ToString(), SummaryDate, SummaryDate); + var key = GetKey(appId); + + var entries = await usageStore.QueryAsync(key, SummaryDate, SummaryDate); return (long)entries.Select(x => x.Counters.Get(CounterTotalSize)).FirstOrDefault(); } @@ -43,7 +45,7 @@ namespace Squidex.Domain.Apps.Entities.Assets { var enriched = new List(); - var usagesFlat = await usageStore.QueryAsync(appId.ToString(), fromDate, toDate); + var usagesFlat = await usageStore.QueryAsync(GetKey(appId), fromDate, toDate); for (var date = fromDate; date <= toDate; date = date.AddDays(1)) { diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs index dfefeb9e1..5f8bfceae 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs @@ -60,11 +60,16 @@ namespace Squidex.Domain.Apps.Entities.Assets [CounterTotalCount] = count }; - var key = appId.ToString(); + var key = GetKey(appId); return Task.WhenAll( usageStore.TrackUsagesAsync(new UsageUpdate(date, key, Category, counters)), usageStore.TrackUsagesAsync(new UsageUpdate(SummaryDate, key, Category, counters))); } + + private static string GetKey(Guid appId) + { + return $"{appId}_Assets"; + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs b/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs index d60e3070c..ebf79a790 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.State; @@ -89,16 +88,16 @@ namespace Squidex.Domain.Apps.Entities.Assets private async Task RestoreTagsAsync(Guid appId, BackupReader reader) { - var tags = await reader.ReadJsonAttachmentAsync(TagsFile); + var tags = await reader.ReadJsonAttachmentAsync(TagsFile); - await tagService.RebuildTagsAsync(appId, TagGroups.Assets, tags.ToObject()); + await tagService.RebuildTagsAsync(appId, TagGroups.Assets, tags); } private async Task BackupTagsAsync(Guid appId, BackupWriter writer) { var tags = await tagService.GetExportableTagsAsync(appId, TagGroups.Assets); - await writer.WriteJsonAsync(TagsFile, JObject.FromObject(tags)); + await writer.WriteJsonAsync(TagsFile, tags); } private Task WriteAssetAsync(Guid assetId, long fileVersion, BackupWriter writer) diff --git a/src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs b/src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs index 44693da64..e9ebd33ec 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs @@ -7,7 +7,7 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Runtime.Serialization; using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Assets; @@ -20,37 +20,37 @@ namespace Squidex.Domain.Apps.Entities.Assets.State { public class AssetState : DomainObjectState, IAssetEntity { - [JsonProperty] + [DataMember] public NamedId AppId { get; set; } - [JsonProperty] + [DataMember] public string FileName { get; set; } - [JsonProperty] + [DataMember] public string MimeType { get; set; } - [JsonProperty] + [DataMember] public long FileVersion { get; set; } - [JsonProperty] + [DataMember] public long FileSize { get; set; } - [JsonProperty] + [DataMember] public long TotalSize { get; set; } - [JsonProperty] + [DataMember] public bool IsImage { get; set; } - [JsonProperty] + [DataMember] public int? PixelWidth { get; set; } - [JsonProperty] + [DataMember] public int? PixelHeight { get; set; } - [JsonProperty] + [DataMember] public bool IsDeleted { get; set; } - [JsonProperty] + [DataMember] public HashSet Tags { get; set; } Guid IAssetInfo.AssetId diff --git a/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs b/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs index 21067ce66..a8a660bc2 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs @@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.States; @@ -34,6 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Backup private readonly IBackupArchiveLocation backupArchiveLocation; private readonly IClock clock; private readonly IEnumerable handlers; + private readonly IJsonSerializer serializer; private readonly IEventDataFormatter eventDataFormatter; private readonly IEventStore eventStore; private readonly ISemanticLog log; @@ -51,6 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Backup IEventStore eventStore, IEventDataFormatter eventDataFormatter, IEnumerable handlers, + IJsonSerializer serializer, ISemanticLog log, IStore store) { @@ -60,6 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Backup Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); Guard.NotNull(handlers, nameof(handlers)); + Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(store, nameof(store)); Guard.NotNull(log, nameof(log)); @@ -69,6 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Backup this.eventStore = eventStore; this.eventDataFormatter = eventDataFormatter; this.handlers = handlers; + this.serializer = serializer; this.store = store; this.log = log; } @@ -139,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Backup { using (var stream = await backupArchiveLocation.OpenStreamAsync(job.Id)) { - using (var writer = new BackupWriter(stream, true)) + using (var writer = new BackupWriter(serializer, stream, true)) { await eventStore.QueryAsync(async storedEvent => { diff --git a/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs b/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs index 32b8faf10..3aa5bc9c5 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs @@ -8,21 +8,25 @@ using System; using System.IO; using System.IO.Compression; +using System.Linq; using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Entities.Backup.Helpers; +using Squidex.Domain.Apps.Entities.Backup.Model; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.States; +#pragma warning disable SA1401 // Fields must be private + namespace Squidex.Domain.Apps.Entities.Backup { public sealed class BackupReader : DisposableObjectBase { - private static readonly JsonSerializer Serializer = new JsonSerializer(); private readonly GuidMapper guidMapper = new GuidMapper(); private readonly ZipArchive archive; + private readonly IJsonSerializer serializer; private int readEvents; private int readAttachments; @@ -36,8 +40,12 @@ namespace Squidex.Domain.Apps.Entities.Backup get { return readAttachments; } } - public BackupReader(Stream stream) + public BackupReader(IJsonSerializer serializer, Stream stream) { + Guard.NotNull(serializer, nameof(serializer)); + + this.serializer = serializer; + archive = new ZipArchive(stream, ZipArchiveMode.Read, false); } @@ -54,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Backup return guidMapper.OldGuid(newId); } - public async Task ReadJsonAttachmentAsync(string name) + public Task ReadJsonAttachmentAsync(string name) { Guard.NotNullOrEmpty(name, nameof(name)); @@ -65,24 +73,16 @@ namespace Squidex.Domain.Apps.Entities.Backup throw new FileNotFoundException("Cannot find attachment.", name); } - JToken result; + T result; using (var stream = attachmentEntry.Open()) { - using (var textReader = new StreamReader(stream)) - { - using (var jsonReader = new JsonTextReader(textReader)) - { - result = await JToken.ReadFromAsync(jsonReader); - - guidMapper.NewGuids(result); - } - } + result = serializer.Deserialize(stream, null, guidMapper.NewGuidOrValue); } readAttachments++; - return result; + return Task.FromResult(result); } public async Task ReadBlobAsync(string name, Func handler) @@ -105,9 +105,10 @@ namespace Squidex.Domain.Apps.Entities.Backup readAttachments++; } - public async Task ReadEventsAsync(IStreamNameResolver streamNameResolver, Func handler) + public async Task ReadEventsAsync(IStreamNameResolver streamNameResolver, IEventDataFormatter formatter, Func<(string Stream, Envelope Event), Task> handler) { Guard.NotNull(handler, nameof(handler)); + Guard.NotNull(formatter, nameof(formatter)); Guard.NotNull(streamNameResolver, nameof(streamNameResolver)); while (true) @@ -121,29 +122,34 @@ namespace Squidex.Domain.Apps.Entities.Backup using (var stream = eventEntry.Open()) { - using (var textReader = new StreamReader(stream)) - { - using (var jsonReader = new JsonTextReader(textReader)) - { - var storedEvent = Serializer.Deserialize(jsonReader); + var (streamName, data) = serializer.Deserialize(stream).ToEvent(); - storedEvent.Data.Payload = guidMapper.NewGuids(storedEvent.Data.Payload); - storedEvent.Data.Metadata = guidMapper.NewGuids(storedEvent.Data.Metadata); + MapHeaders(data); - var streamName = streamNameResolver.WithNewId(storedEvent.StreamName, guidMapper.NewGuidString); + var eventStream = streamNameResolver.WithNewId(streamName, guidMapper.NewGuidOrNull); + var eventEnvelope = formatter.Parse(data, guidMapper.NewGuidOrValue); - storedEvent = new StoredEvent(streamName, - storedEvent.EventPosition, - storedEvent.EventStreamNumber, - storedEvent.Data); - - await handler(storedEvent); - } - } + await handler((eventStream, eventEnvelope)); } readEvents++; } } + + private void MapHeaders(EventData data) + { + foreach (var kvp in data.Headers.ToList()) + { + if (kvp.Value.Type == JsonValueType.String) + { + var newGuid = guidMapper.NewGuidOrNull(kvp.Value.ToString()); + + if (newGuid != null) + { + data.Headers.Add(kvp.Key, newGuid); + } + } + } + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Backup/BackupVersion.cs b/src/Squidex.Domain.Apps.Entities/Backup/BackupVersion.cs new file mode 100644 index 000000000..e6cddcd7d --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Backup/BackupVersion.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Entities.Backup +{ + public enum BackupVersion + { + V2, + V1 + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs b/src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs index fa0e793af..0f3033bcb 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/BackupWriter.cs @@ -9,18 +9,20 @@ using System; using System.IO; using System.IO.Compression; using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Entities.Backup.Helpers; +using Squidex.Domain.Apps.Entities.Backup.Model; using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Entities.Backup { public sealed class BackupWriter : DisposableObjectBase { - private static readonly JsonSerializer Serializer = new JsonSerializer(); private readonly ZipArchive archive; + private readonly IJsonSerializer serializer; + private readonly Func converter; private int writtenEvents; private int writtenAttachments; @@ -34,8 +36,17 @@ namespace Squidex.Domain.Apps.Entities.Backup get { return writtenAttachments; } } - public BackupWriter(Stream stream, bool keepOpen = false) + public BackupWriter(IJsonSerializer serializer, Stream stream, bool keepOpen = false, BackupVersion version = BackupVersion.V2) { + Guard.NotNull(serializer, nameof(serializer)); + + this.serializer = serializer; + + converter = + version == BackupVersion.V1 ? + new Func(CompatibleStoredEvent.V1) : + new Func(CompatibleStoredEvent.V2); + archive = new ZipArchive(stream, ZipArchiveMode.Create, keepOpen); } @@ -47,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Backup } } - public async Task WriteJsonAsync(string name, JToken value) + public Task WriteJsonAsync(string name, object value) { Guard.NotNullOrEmpty(name, nameof(name)); @@ -55,16 +66,12 @@ namespace Squidex.Domain.Apps.Entities.Backup using (var stream = attachmentEntry.Open()) { - using (var textWriter = new StreamWriter(stream)) - { - using (var jsonWriter = new JsonTextWriter(textWriter)) - { - await value.WriteToAsync(jsonWriter); - } - } + serializer.Serialize(value, stream); } writtenAttachments++; + + return TaskHelper.Done; } public async Task WriteBlobAsync(string name, Func handler) @@ -90,13 +97,9 @@ namespace Squidex.Domain.Apps.Entities.Backup using (var stream = eventEntry.Open()) { - using (var textWriter = new StreamWriter(stream)) - { - using (var jsonWriter = new JsonTextWriter(textWriter)) - { - Serializer.Serialize(jsonWriter, storedEvent); - } - } + var @event = converter(storedEvent); + + serializer.Serialize(@event, stream); } writtenEvents++; diff --git a/src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs b/src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs index c0f7ac827..4b1916655 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/GuidMapper.cs @@ -7,149 +7,87 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json.Linq; using Squidex.Infrastructure; namespace Squidex.Domain.Apps.Entities.Backup { - public sealed class GuidMapper + internal sealed class GuidMapper { private static readonly int GuidLength = Guid.Empty.ToString().Length; - private readonly List<(JObject Source, string NewKey, string OldKey)> mappings = new List<(JObject Source, string NewKey, string OldKey)>(); private readonly Dictionary oldToNewGuid = new Dictionary(); private readonly Dictionary newToOldGuid = new Dictionary(); - - public Guid NewGuid(Guid oldGuid) - { - return oldToNewGuid.GetOrDefault(oldGuid); - } + private readonly Dictionary strings = new Dictionary(); public Guid OldGuid(Guid newGuid) { - return newToOldGuid.GetOrDefault(newGuid); + return newToOldGuid.GetOrCreate(newGuid, x => x); } - public string NewGuidString(string key) + public string NewGuidOrNull(string value) { - if (Guid.TryParse(key, out var guid)) + if (TryGenerateNewGuidString(value, out var result) || TryGenerateNewNamedId(value, out result)) { - return GenerateNewGuid(guid).ToString(); + return result; } return null; } - public JToken NewGuids(JToken jToken) - { - var result = NewGuidsCore(jToken); - - if (mappings.Count > 0) - { - foreach (var mapping in mappings) - { - if (mapping.Source.TryGetValue(mapping.OldKey, out var value)) - { - mapping.Source.Remove(mapping.OldKey); - mapping.Source[mapping.NewKey] = value; - } - } - - mappings.Clear(); - } - - return result; - } - - private JToken NewGuidsCore(JToken jToken) + public string NewGuidOrValue(string value) { - switch (jToken.Type) + if (TryGenerateNewGuidString(value, out var result) || TryGenerateNewNamedId(value, out result)) { - case JTokenType.String: - if (TryConvertString(jToken.ToString(), out var result)) - { - return result; - } - - break; - case JTokenType.Guid: - return GenerateNewGuid((Guid)jToken); - case JTokenType.Object: - NewGuidsCore((JObject)jToken); - break; - case JTokenType.Array: - NewGuidsCore((JArray)jToken); - break; + return result; } - return jToken; - } - - private void NewGuidsCore(JArray jArray) - { - for (var i = 0; i < jArray.Count; i++) - { - jArray[i] = NewGuidsCore(jArray[i]); - } + return value; } - private void NewGuidsCore(JObject jObject) + private bool TryGenerateNewGuidString(string value, out string result) { - foreach (var jProperty in jObject.Properties()) + if (value.Length == GuidLength) { - var newValue = NewGuidsCore(jProperty.Value); - - if (!ReferenceEquals(newValue, jProperty.Value)) - { - jProperty.Value = newValue; - } - - if (TryConvertString(jProperty.Name, out var newKey)) + if (strings.TryGetValue(value, out result)) { - mappings.Add((jObject, newKey, jProperty.Name)); + return true; } - } - } - - private bool TryConvertString(string value, out string result) - { - return TryGenerateNewGuidString(value, out result) || TryGenerateNewNamedId(value, out result); - } - private bool TryGenerateNewGuidString(string value, out string result) - { - result = null; - - if (value.Length == GuidLength) - { if (Guid.TryParse(value, out var guid)) { var newGuid = GenerateNewGuid(guid); - result = newGuid.ToString(); + strings[value] = result = newGuid.ToString(); return true; } } + result = null; + return false; } private bool TryGenerateNewNamedId(string value, out string result) { - result = null; - - if (value.Length > GuidLength && value[GuidLength] == ',') + if (value.Length > GuidLength) { - if (Guid.TryParse(value.Substring(0, GuidLength), out var guid)) + if (strings.TryGetValue(value, out result)) { - var newGuid = GenerateNewGuid(guid); + return true; + } + + if (NamedId.TryParse(value, Guid.TryParse, out var namedId)) + { + var newGuid = GenerateNewGuid(namedId.Id); - result = newGuid + value.Substring(GuidLength); + strings[value] = result = new NamedId(newGuid, namedId.Name).ToString(); return true; } } + result = null; + return false; } diff --git a/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Downloader.cs b/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Downloader.cs index f002efa81..a567eb717 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Downloader.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/Helpers/Downloader.cs @@ -9,6 +9,7 @@ using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; +using Squidex.Infrastructure.Json; namespace Squidex.Domain.Apps.Entities.Backup.Helpers { @@ -39,7 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Backup.Helpers } } - public static async Task OpenArchiveAsync(this IBackupArchiveLocation backupArchiveLocation, Guid id) + public static async Task OpenArchiveAsync(this IBackupArchiveLocation backupArchiveLocation, Guid id, IJsonSerializer serializer) { Stream stream = null; @@ -47,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Backup.Helpers { stream = await backupArchiveLocation.OpenStreamAsync(id); - return new BackupReader(stream); + return new BackupReader(serializer, stream); } catch (IOException) { diff --git a/src/Squidex.Domain.Apps.Entities/Backup/Model/CompatibleStoredEvent.cs b/src/Squidex.Domain.Apps.Entities/Backup/Model/CompatibleStoredEvent.cs new file mode 100644 index 000000000..ac84f1bb7 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Backup/Model/CompatibleStoredEvent.cs @@ -0,0 +1,116 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Squidex.Infrastructure.EventSourcing; + +#pragma warning disable SA1401 // Fields must be private + +namespace Squidex.Domain.Apps.Entities.Backup.Model +{ + public sealed class CompatibleStoredEvent + { + [JsonProperty("n")] + public NewEvent NewEvent; + + [JsonProperty] + public string StreamName; + + [JsonProperty] + public string EventPosition; + + [JsonProperty] + public long EventStreamNumber; + + [JsonProperty] + public CompatibleEventData Data; + + public static CompatibleStoredEvent V1(StoredEvent stored) + { + return new CompatibleStoredEvent + { + Data = CompatibleEventData.V1(stored.Data), + EventPosition = stored.EventPosition, + EventStreamNumber = stored.EventStreamNumber, + StreamName = stored.StreamName + }; + } + + public static CompatibleStoredEvent V2(StoredEvent stored) + { + return new CompatibleStoredEvent { NewEvent = NewEvent.V2(stored) }; + } + + public (string Stream, EventData Data) ToEvent() + { + if (NewEvent != null) + { + return NewEvent.ToEvent(); + } + else + { + return (StreamName, Data.ToData()); + } + } + } + + public sealed class CompatibleEventData + { + [JsonProperty] + public string Type; + + [JsonProperty] + public JRaw Payload; + + [JsonProperty] + public EnvelopeHeaders Metadata; + + public static CompatibleEventData V1(EventData data) + { + var payload = new JRaw(data.Payload); + + return new CompatibleEventData { Type = data.Type, Payload = payload, Metadata = data.Headers }; + } + + public EventData ToData() + { + return new EventData(Type, Metadata, Payload.ToString()); + } + } + + public sealed class NewEvent + { + [JsonProperty("t")] + public string EventType; + + [JsonProperty("s")] + public string StreamName; + + [JsonProperty("p")] + public string EventPayload; + + [JsonProperty("h")] + public EnvelopeHeaders EventHeaders; + + public static NewEvent V2(StoredEvent stored) + { + return new NewEvent + { + EventType = stored.Data.Type, + EventHeaders = stored.Data.Headers, + EventPayload = stored.Data.Payload, + StreamName = stored.StreamName + }; + } + + public (string Stream, EventData Data) ToEvent() + { + return (StreamName, new EventData(EventType, EventHeaders, EventPayload)); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs b/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs index 0a7536d1b..38bc2394f 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/RestoreGrain.cs @@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Events.Apps; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.States; @@ -31,6 +32,7 @@ namespace Squidex.Domain.Apps.Entities.Backup private readonly IClock clock; private readonly ICommandBus commandBus; private readonly IEnumerable handlers; + private readonly IJsonSerializer serializer; private readonly IEventStore eventStore; private readonly IEventDataFormatter eventDataFormatter; private readonly ISemanticLog log; @@ -51,6 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Backup IEventStore eventStore, IEventDataFormatter eventDataFormatter, IEnumerable handlers, + IJsonSerializer serializer, ISemanticLog log, IStreamNameResolver streamNameResolver, IStore store) @@ -61,6 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Backup Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); Guard.NotNull(handlers, nameof(handlers)); + Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(store, nameof(store)); Guard.NotNull(streamNameResolver, nameof(streamNameResolver)); Guard.NotNull(log, nameof(log)); @@ -71,6 +75,7 @@ namespace Squidex.Domain.Apps.Entities.Backup this.eventStore = eventStore; this.eventDataFormatter = eventDataFormatter; this.handlers = handlers; + this.serializer = serializer; this.store = store; this.streamNameResolver = streamNameResolver; this.log = log; @@ -161,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Backup await DownloadAsync(); } - using (var reader = await backupArchiveLocation.OpenArchiveAsync(CurrentJob.Id)) + using (var reader = await backupArchiveLocation.OpenArchiveAsync(CurrentJob.Id, serializer)) { using (Profiler.Trace("ReadEvents")) { @@ -189,6 +194,8 @@ namespace Squidex.Domain.Apps.Entities.Backup } } + await AssignContributorAsync(); + CurrentJob.Status = JobStatus.Completed; Log("Completed, Yeah!"); @@ -247,6 +254,8 @@ namespace Squidex.Domain.Apps.Entities.Backup FromRestore = true, Role = Role.Owner }); + + Log("Assigned current user."); } private async Task CleanupAsync() @@ -273,17 +282,15 @@ namespace Squidex.Domain.Apps.Entities.Backup private async Task ReadEventsAsync(BackupReader reader) { - await reader.ReadEventsAsync(streamNameResolver, async storedEvent => + await reader.ReadEventsAsync(streamNameResolver, eventDataFormatter, async storedEvent => { - var @event = eventDataFormatter.Parse(storedEvent.Data); - - await HandleEventAsync(reader, storedEvent, @event); + await HandleEventAsync(reader, storedEvent.Stream, storedEvent.Event); }); - Log("Reading events completed."); + Log($"Reading {reader.ReadEvents} events and {reader.ReadAttachments} attachments completed.", true); } - private async Task HandleEventAsync(BackupReader reader, StoredEvent storedEvent, Envelope @event) + private async Task HandleEventAsync(BackupReader reader, string stream, Envelope @event) { if (@event.Payload is SquidexEvent squidexEvent) { @@ -316,7 +323,7 @@ namespace Squidex.Domain.Apps.Entities.Backup var eventData = eventDataFormatter.ToEventData(@event, @event.Headers.CommitId()); var eventCommit = new List { eventData }; - await eventStore.AppendAsync(Guid.NewGuid(), storedEvent.StreamName, eventCommit); + await eventStore.AppendAsync(Guid.NewGuid(), stream, eventCommit); Log($"Read {reader.ReadEvents} events and {reader.ReadAttachments} attachments.", true); } diff --git a/src/Squidex.Domain.Apps.Entities/Backup/State/BackupState.cs b/src/Squidex.Domain.Apps.Entities/Backup/State/BackupState.cs index e75eef133..75cf87a13 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/State/BackupState.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/State/BackupState.cs @@ -6,13 +6,13 @@ // ========================================================================== using System.Collections.Generic; -using Newtonsoft.Json; +using System.Runtime.Serialization; namespace Squidex.Domain.Apps.Entities.Backup.State { public sealed class BackupState { - [JsonProperty] + [DataMember] public List Jobs { get; } = new List(); } } diff --git a/src/Squidex.Domain.Apps.Entities/Backup/State/BackupStateJob.cs b/src/Squidex.Domain.Apps.Entities/Backup/State/BackupStateJob.cs index 6da5e9dfa..209a6f1c7 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/State/BackupStateJob.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/State/BackupStateJob.cs @@ -6,29 +6,29 @@ // ========================================================================== using System; -using Newtonsoft.Json; +using System.Runtime.Serialization; using NodaTime; namespace Squidex.Domain.Apps.Entities.Backup.State { public sealed class BackupStateJob : IBackupJob { - [JsonProperty] + [DataMember] public Guid Id { get; set; } - [JsonProperty] + [DataMember] public Instant Started { get; set; } - [JsonProperty] + [DataMember] public Instant? Stopped { get; set; } - [JsonProperty] + [DataMember] public int HandledEvents { get; set; } - [JsonProperty] + [DataMember] public int HandledAssets { get; set; } - [JsonProperty] + [DataMember] public JobStatus Status { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Backup/State/RestoreState.cs b/src/Squidex.Domain.Apps.Entities/Backup/State/RestoreState.cs index d86a658fe..bbfc4db36 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/State/RestoreState.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/State/RestoreState.cs @@ -5,13 +5,13 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json; +using System.Runtime.Serialization; namespace Squidex.Domain.Apps.Entities.Backup.State { public class RestoreState { - [JsonProperty] + [DataMember] public RestoreStateJob Job { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Backup/State/RestoreStateJob.cs b/src/Squidex.Domain.Apps.Entities/Backup/State/RestoreStateJob.cs index f3fd17042..edefb80dd 100644 --- a/src/Squidex.Domain.Apps.Entities/Backup/State/RestoreStateJob.cs +++ b/src/Squidex.Domain.Apps.Entities/Backup/State/RestoreStateJob.cs @@ -7,38 +7,39 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Runtime.Serialization; using NodaTime; namespace Squidex.Domain.Apps.Entities.Backup.State { + [DataContract] public sealed class RestoreStateJob : IRestoreJob { - [JsonProperty] + [DataMember] public string AppName { get; set; } - [JsonProperty] + [DataMember] public Guid Id { get; set; } - [JsonProperty] + [DataMember] public Guid AppId { get; set; } - [JsonProperty] + [DataMember] public Uri Url { get; set; } - [JsonProperty] + [DataMember] public string NewAppName { get; set; } - [JsonProperty] + [DataMember] public Instant Started { get; set; } - [JsonProperty] + [DataMember] public Instant? Stopped { get; set; } - [JsonProperty] + [DataMember] public List Log { get; set; } = new List(); - [JsonProperty] + [DataMember] public JobStatus Status { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs index 85d847e62..06cb8efb6 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs @@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Contents case CreateContent createContent: return CreateReturnAsync(createContent, async c => { - var ctx = await CreateContext(c.AppId.Id, c.SchemaId.Id, () => "Failed to create content."); + var ctx = await CreateContext(c.AppId.Id, c.SchemaId.Id, Guid.Empty, () => "Failed to create content."); GuardContent.CanCreate(ctx.Schema, c); @@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { try { - var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, () => "Failed to change content."); + var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, Snapshot.Id, () => "Failed to change content."); GuardContent.CanChangeContentStatus(ctx.Schema, Snapshot.IsPending, Snapshot.Status, c); @@ -162,7 +162,7 @@ namespace Squidex.Domain.Apps.Entities.Contents case DeleteContent deleteContent: return UpdateAsync(deleteContent, async c => { - var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, () => "Failed to delete content."); + var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, Snapshot.Id, () => "Failed to delete content."); GuardContent.CanDelete(ctx.Schema, c); @@ -197,7 +197,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (!currentData.Equals(newData)) { - var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, () => "Failed to update content."); + var ctx = await CreateContext(Snapshot.AppId.Id, Snapshot.SchemaId.Id, Snapshot.Id, () => "Failed to update content."); if (partial) { @@ -301,11 +301,10 @@ namespace Squidex.Domain.Apps.Entities.Contents return Snapshot.Apply(@event); } - private async Task CreateContext(Guid appId, Guid schemaId, Func message) + private async Task CreateContext(Guid appId, Guid schemaId, Guid contentId, Func message) { var operationContext = - await ContentOperationContext.CreateAsync( - appId, schemaId, + await ContentOperationContext.CreateAsync(appId, schemaId, contentId, appProvider, assetRepository, contentRepository, scriptEngine, message); return operationContext; diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs index 5ca66b9ef..6613a5276 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.EnrichContent; @@ -18,6 +17,7 @@ using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Entities.Contents @@ -29,6 +29,8 @@ namespace Squidex.Domain.Apps.Entities.Contents private IScriptEngine scriptEngine; private ISchemaEntity schemaEntity; private IAppEntity appEntity; + private Guid contentId; + private Guid schemaId; private Func message; public ISchemaEntity Schema @@ -39,6 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Contents public static async Task CreateAsync( Guid appId, Guid schemaId, + Guid contentId, IAppProvider appProvider, IAssetRepository assetRepository, IContentRepository contentRepository, @@ -51,8 +54,10 @@ namespace Squidex.Domain.Apps.Entities.Contents { appEntity = appEntity, assetRepository = assetRepository, + contentId = contentId, contentRepository = contentRepository, message = message, + schemaId = schemaId, schemaEntity = schemaEntity, scriptEngine = scriptEngine }; @@ -106,7 +111,7 @@ namespace Squidex.Domain.Apps.Entities.Contents private ValidationContext CreateValidationContext() { - return new ValidationContext((contentIds, schemaId) => QueryContentsAsync(schemaId, contentIds), QueryAssetsAsync); + return new ValidationContext(contentId, schemaId, (sid, filterNode) => QueryContentsAsync(sid, filterNode), QueryAssetsAsync); } private async Task> QueryAssetsAsync(IEnumerable assetIds) @@ -114,9 +119,9 @@ namespace Squidex.Domain.Apps.Entities.Contents return await assetRepository.QueryAsync(appEntity.Id, new HashSet(assetIds)); } - private async Task> QueryContentsAsync(Guid schemaId, IEnumerable contentIds) + private async Task> QueryContentsAsync(Guid filterSchemaId, FilterNode filterNode) { - return await contentRepository.QueryNotFoundAsync(appEntity.Id, schemaId, contentIds.ToList()); + return await contentRepository.QueryIdsAsync(appEntity.Id, filterSchemaId, filterNode); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs index dce94972e..bdb223f40 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs @@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var result = await Task.WhenAll(queries.Select(q => QueryInternalAsync(model, ctx, q))); - return (result.Any(x => x.HasError), result.Select(x => x.Response).ToArray()); + return (result.Any(x => x.HasError), result.ToArray(x => x.Response)); } public async Task<(bool HasError, object Response)> QueryAsync(QueryContext context, GraphQLQuery query) diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs index 2c1242877..d5b1316fb 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs @@ -8,8 +8,9 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Infrastructure.Json.Objects; + namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { public sealed class GraphQLExecutionContext : QueryExecutionContext @@ -25,29 +26,29 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL UrlGenerator = urlGenerator; } - public Task> GetReferencedAssetsAsync(JToken value) + public Task> GetReferencedAssetsAsync(IJsonValue value) { var ids = ParseIds(value); return GetReferencedAssetsAsync(ids); } - public Task> GetReferencedContentsAsync(Guid schemaId, JToken value) + public Task> GetReferencedContentsAsync(Guid schemaId, IJsonValue value) { var ids = ParseIds(value); return GetReferencedContentsAsync(schemaId, ids); } - private static ICollection ParseIds(JToken value) + private static ICollection ParseIds(IJsonValue value) { try { var result = new List(); - if (value is JArray) + if (value is JsonArray array) { - foreach (var id in value) + foreach (var id in array) { result.Add(Guid.Parse(id.ToString())); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs index a450cf448..18b9beff3 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AllTypes.cs @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types public static readonly IGraphType Guid = new GuidGraphType2(); - public static readonly IGraphType Date = new DateTimeGraphType(); + public static readonly IGraphType Date = new InstantGraphType(); public static readonly IGraphType Json = new JsonGraphType(); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs index 47e32ed04..c23f2c69c 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/AssetGraphType.cs @@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = "created", ResolvedType = AllTypes.NonNullDate, - Resolver = Resolve(x => x.Created.ToDateTimeUtc()), + Resolver = Resolve(x => x.Created), Description = "The date and time when the asset has been created." }); @@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = "lastModified", ResolvedType = AllTypes.NonNullDate, - Resolver = Resolve(x => x.LastModified.ToDateTimeUtc()), + Resolver = Resolve(x => x.LastModified), Description = "The date and time when the asset has been modified last." }); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs index cb77d343d..20358dd65 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentDataGraphType.cs @@ -9,10 +9,10 @@ using System.Collections.Generic; using System.Linq; using GraphQL.Resolvers; using GraphQL.Types; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types fieldGraphType.Description = $"The structure of the {fieldName} field of the {schemaName} content type."; - var fieldResolver = new FuncFieldResolver>(c => + var fieldResolver = new FuncFieldResolver>(c => { return c.Source.GetOrDefault(field.Name); }); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs index 40990b9ad..31f68a324 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/ContentGraphType.cs @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = "created", ResolvedType = AllTypes.NonNullDate, - Resolver = Resolve(x => x.Created.ToDateTimeUtc()), + Resolver = Resolve(x => x.Created), Description = $"The date and time when the {schemaName} content has been created." }); @@ -58,7 +58,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { Name = "lastModified", ResolvedType = AllTypes.NonNullDate, - Resolver = Resolve(x => x.LastModified.ToDateTimeUtc()), + Resolver = Resolve(x => x.LastModified), Description = $"The date and time when the {schemaName} content has been modified last." }); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs index b0dc095d2..35c0a1b2e 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/NestedGraphType.cs @@ -8,14 +8,14 @@ using System.Linq; using GraphQL.Resolvers; using GraphQL.Types; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { - public sealed class NestedGraphType : ObjectGraphType + public sealed class NestedGraphType : ObjectGraphType { public NestedGraphType(IGraphModel model, ISchemaEntity schema, IArrayField field) { @@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { var resolver = new FuncFieldResolver(c => { - if (((JObject)c.Source).TryGetValue(nestedField.Name, out var value)) + if (((JsonObject)c.Source).TryGetValue(nestedField.Name, out var value)) { return fieldInfo.Resolver(value, c); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs index fa1e34514..2ed69033c 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/QueryGraphTypeVisitor.cs @@ -7,13 +7,13 @@ using System; using GraphQL.Types; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types { - public delegate object ValueResolver(JToken value, ResolveFieldContext context); + public delegate object ValueResolver(IJsonValue value, ResolveFieldContext context); public sealed class QueryGraphTypeVisitor : IFieldVisitor<(IGraphType ResolveType, ValueResolver Resolver)> { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InstantGraphType.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InstantGraphType.cs new file mode 100644 index 000000000..2f45fe6a9 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InstantGraphType.cs @@ -0,0 +1,41 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using GraphQL.Language.AST; +using GraphQL.Types; +using NodaTime.Text; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils +{ + public sealed class InstantGraphType : DateGraphType + { + public override object Serialize(object value) + { + return ParseValue(value); + } + + public override object ParseValue(object value) + { + return InstantPattern.General.Parse(value.ToString()).Value; + } + + public override object ParseLiteral(IValue value) + { + if (value is InstantValue timeValue) + { + return ParseValue(timeValue.Value); + } + + if (value is StringValue stringValue) + { + return ParseValue(stringValue.Value); + } + + return null; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InstantValue.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InstantValue.cs new file mode 100644 index 000000000..1af6d2e92 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/InstantValue.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using GraphQL.Language.AST; +using NodaTime; + +namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils +{ + public sealed class InstantValue : ValueNode + { + public InstantValue(Instant value) + { + Value = value; + } + + protected override bool Equals(ValueNode node) + { + return Value.Equals(node.Value); + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonConverter.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonConverter.cs index 62bc939fb..a433a5fb3 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonConverter.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonConverter.cs @@ -7,7 +7,7 @@ using GraphQL.Language.AST; using GraphQL.Types; -using Newtonsoft.Json.Linq; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils { @@ -21,12 +21,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils public IValue Convert(object value, IGraphType type) { - return new JsonValue(value as JObject); + return new JsonValue(value as JsonObject); } public bool Matches(object value, IGraphType type) { - return value is JObject; + return value is JsonObject; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonValue.cs b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonValue.cs index 4977e33d2..01449380f 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonValue.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Utils/JsonValue.cs @@ -6,18 +6,18 @@ // ========================================================================== using GraphQL.Language.AST; -using Newtonsoft.Json.Linq; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils { - public sealed class JsonValue : ValueNode + public sealed class JsonValue : ValueNode { - public JsonValue(JObject value) + public JsonValue(IJsonValue value) { Value = value; } - protected override bool Equals(ValueNode node) + protected override bool Equals(ValueNode node) { return false; } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs index 9f65e239b..e82d39932 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Query query); - Task> QueryNotFoundAsync(Guid appId, Guid schemaId, IList ids); + Task> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode filterNode); Task FindContentAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Guid id); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs b/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs index 15f2fc357..56936bb0a 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/State/ContentState.cs @@ -6,7 +6,7 @@ // ========================================================================== using System; -using Newtonsoft.Json; +using System.Runtime.Serialization; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Contents; @@ -19,28 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.State { public class ContentState : DomainObjectState, IContentEntity { - [JsonProperty] + [DataMember] public NamedId AppId { get; set; } - [JsonProperty] + [DataMember] public NamedId SchemaId { get; set; } - [JsonProperty] + [DataMember] public NamedContentData Data { get; set; } - [JsonProperty] + [DataMember] public NamedContentData DataDraft { get; set; } - [JsonProperty] + [DataMember] public ScheduleJob ScheduleJob { get; set; } - [JsonProperty] + [DataMember] public bool IsPending { get; set; } - [JsonProperty] + [DataMember] public bool IsDeleted { get; set; } - [JsonProperty] + [DataMember] public Status Status { get; set; } protected void On(ContentCreated @event) diff --git a/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs b/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs index 13ce32acc..58b6341b5 100644 --- a/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs +++ b/src/Squidex.Domain.Apps.Entities/DomainObjectState.cs @@ -6,7 +6,7 @@ // ========================================================================== using System; -using Newtonsoft.Json; +using System.Runtime.Serialization; using NodaTime; using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; @@ -24,22 +24,22 @@ namespace Squidex.Domain.Apps.Entities IUpdateableEntityWithLastModifiedBy where T : Cloneable { - [JsonProperty] + [DataMember] public Guid Id { get; set; } - [JsonProperty] + [DataMember] public RefToken CreatedBy { get; set; } - [JsonProperty] + [DataMember] public RefToken LastModifiedBy { get; set; } - [JsonProperty] + [DataMember] public Instant Created { get; set; } - [JsonProperty] + [DataMember] public Instant LastModified { get; set; } - [JsonProperty] + [DataMember] public long Version { get; set; } = EtagVersion.Empty; public T Clone() diff --git a/src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs b/src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs index 80da5ed16..a12f9a744 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/State/RuleState.cs @@ -6,7 +6,7 @@ // ========================================================================== using System; -using Newtonsoft.Json; +using System.Runtime.Serialization; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Rules; @@ -20,13 +20,13 @@ namespace Squidex.Domain.Apps.Entities.Rules.State [CollectionName("Rules")] public class RuleState : DomainObjectState, IRuleEntity { - [JsonProperty] + [DataMember] public NamedId AppId { get; set; } - [JsonProperty] + [DataMember] public Rule RuleDef { get; set; } - [JsonProperty] + [DataMember] public bool IsDeleted { get; set; } protected void On(RuleCreated @event) diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs index 8beb2b8f1..19eea457e 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs @@ -243,6 +243,18 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards public IEnumerable Visit(TagsFieldProperties properties) { + if (!properties.Editor.IsEnumValue()) + { + yield return new ValidationError("Editor is not a valid value.", + nameof(properties.Editor)); + } + + if ((properties.Editor == TagsFieldEditor.Checkboxes || properties.Editor == TagsFieldEditor.Dropdown) && (properties.AllowedValues == null || properties.AllowedValues.Count == 0)) + { + yield return new ValidationError("Checkboxes or dropdown list need allowed values.", + nameof(properties.AllowedValues)); + } + if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value >= properties.MaxItems.Value) { yield return new ValidationError("Max items must be greater than min items.", diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs b/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs index fdec25995..59aebd11e 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/State/SchemaState.cs @@ -6,7 +6,7 @@ // ========================================================================== using System; -using Newtonsoft.Json; +using System.Runtime.Serialization; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Events; @@ -22,43 +22,43 @@ namespace Squidex.Domain.Apps.Entities.Schemas.State [CollectionName("Schemas")] public class SchemaState : DomainObjectState, ISchemaEntity { - [JsonProperty] + [DataMember] public NamedId AppId { get; set; } - [JsonProperty] + [DataMember] public string Name { get; set; } - [JsonProperty] + [DataMember] public string Category { get; set; } - [JsonProperty] + [DataMember] public int TotalFields { get; set; } - [JsonProperty] + [DataMember] public bool IsDeleted { get; set; } - [JsonProperty] + [DataMember] public bool IsSingleton { get; set; } - [JsonProperty] + [DataMember] public string ScriptQuery { get; set; } - [JsonProperty] + [DataMember] public string ScriptCreate { get; set; } - [JsonProperty] + [DataMember] public string ScriptUpdate { get; set; } - [JsonProperty] + [DataMember] public string ScriptDelete { get; set; } - [JsonProperty] + [DataMember] public string ScriptChange { get; set; } - [JsonProperty] + [DataMember] public Schema SchemaDef { get; set; } - [JsonIgnore] + [IgnoreDataMember] public bool IsPublished { get { return SchemaDef.IsPublished; } diff --git a/src/Squidex.Domain.Apps.Events/Rules/RuleCreated.cs b/src/Squidex.Domain.Apps.Events/Rules/RuleCreated.cs index 10679e200..bab06a970 100644 --- a/src/Squidex.Domain.Apps.Events/Rules/RuleCreated.cs +++ b/src/Squidex.Domain.Apps.Events/Rules/RuleCreated.cs @@ -17,4 +17,4 @@ namespace Squidex.Domain.Apps.Events.Rules public RuleAction Action { get; set; } } -} +} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Events/Schemas/FieldAdded.cs b/src/Squidex.Domain.Apps.Events/Schemas/FieldAdded.cs index 7b136e947..d73358c30 100644 --- a/src/Squidex.Domain.Apps.Events/Schemas/FieldAdded.cs +++ b/src/Squidex.Domain.Apps.Events/Schemas/FieldAdded.cs @@ -19,4 +19,4 @@ namespace Squidex.Domain.Apps.Events.Schemas public FieldProperties Properties { get; set; } } -} +} \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs b/src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs index 0673d3b5b..dc2e05ad2 100644 --- a/src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs +++ b/src/Squidex.Domain.Apps.Events/SquidexHeaderExtensions.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Globalization; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Events @@ -15,12 +14,12 @@ namespace Squidex.Domain.Apps.Events { public static Guid AppId(this EnvelopeHeaders headers) { - return headers[SquidexHeaders.AppId].ToGuid(CultureInfo.InvariantCulture); + return headers.GetGuid(SquidexHeaders.AppId); } public static Envelope SetAppId(this Envelope envelope, Guid value) where T : class { - envelope.Headers.Set(SquidexHeaders.AppId, value); + envelope.Headers.Add(SquidexHeaders.AppId, value.ToString()); return envelope; } diff --git a/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs b/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs index 62604760d..8ce972c1f 100644 --- a/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs +++ b/src/Squidex.Domain.Users.MongoDb/MongoUserStore.cs @@ -32,6 +32,7 @@ namespace Squidex.Domain.Users.MongoDb IUserLoginStore, IUserPasswordStore, IUserPhoneNumberStore, + IUserRoleStore, IUserSecurityStampStore, IUserTwoFactorStore, IUserTwoFactorRecoveryCodeStore, diff --git a/src/Squidex.Domain.Users/UserClaimsPrincipalFactoryWithEmail.cs b/src/Squidex.Domain.Users/UserClaimsPrincipalFactoryWithEmail.cs index f0efcdfd9..1d6c0f4e5 100644 --- a/src/Squidex.Domain.Users/UserClaimsPrincipalFactoryWithEmail.cs +++ b/src/Squidex.Domain.Users/UserClaimsPrincipalFactoryWithEmail.cs @@ -11,11 +11,15 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using Squidex.Infrastructure.Security; +using Squidex.Shared; +using Squidex.Shared.Identity; namespace Squidex.Domain.Users { public sealed class UserClaimsPrincipalFactoryWithEmail : UserClaimsPrincipalFactory { + private const string AdministratorRole = "ADMINISTRATOR"; + public UserClaimsPrincipalFactoryWithEmail(UserManager userManager, RoleManager roleManager, IOptions optionsAccessor) : base(userManager, roleManager, optionsAccessor) { @@ -25,7 +29,14 @@ namespace Squidex.Domain.Users { var principal = await base.CreateAsync(user); - principal.Identities.First().AddClaim(new Claim(OpenIdClaims.Email, await UserManager.GetEmailAsync(user))); + var identity = principal.Identities.First(); + + identity.AddClaim(new Claim(OpenIdClaims.Email, await UserManager.GetEmailAsync(user))); + + if (await UserManager.IsInRoleAsync(user, AdministratorRole)) + { + identity.AddClaim(new Claim(SquidexClaimTypes.Permissions, Permissions.Admin)); + } return principal; } diff --git a/src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs b/src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs index 42ef4e206..a4f2d3aea 100644 --- a/src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs +++ b/src/Squidex.Infrastructure.GetEventStore/Diagnostics/GetEventStoreHealthCheck.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using EventStore.ClientAPI; @@ -15,6 +16,11 @@ namespace Squidex.Infrastructure.Diagnostics { private readonly IEventStoreConnection connection; + public IEnumerable Scopes + { + get { yield return HealthCheckScopes.Node; } + } + public GetEventStoreHealthCheck(IEventStoreConnection connection) { Guard.NotNull(connection, nameof(connection)); diff --git a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs index c28abf358..8a2dd111f 100644 --- a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs +++ b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/Formatter.cs @@ -8,20 +8,23 @@ using System; using System.Text; using EventStore.ClientAPI; +using Squidex.Infrastructure.Json; using EventStoreData = EventStore.ClientAPI.EventData; namespace Squidex.Infrastructure.EventSourcing { public static class Formatter { - public static StoredEvent Read(ResolvedEvent resolvedEvent) + public static StoredEvent Read(ResolvedEvent resolvedEvent, IJsonSerializer serializer) { var @event = resolvedEvent.Event; - var body = Encoding.UTF8.GetString(@event.Data); - var meta = Encoding.UTF8.GetString(@event.Metadata); + var metadata = Encoding.UTF8.GetString(@event.Data); - var eventData = new EventData { Type = @event.EventType, Payload = body, Metadata = meta }; + var headersJson = Encoding.UTF8.GetString(@event.Metadata); + var headers = serializer.Deserialize(headersJson); + + var eventData = new EventData(@event.EventType, headers, metadata); return new StoredEvent( @event.EventStreamId, @@ -30,12 +33,14 @@ namespace Squidex.Infrastructure.EventSourcing eventData); } - public static EventStoreData Write(EventData eventData) + public static EventStoreData Write(EventData eventData, IJsonSerializer serializer) { - var body = Encoding.UTF8.GetBytes(eventData.Payload.ToString()); - var meta = Encoding.UTF8.GetBytes(eventData.Metadata.ToString()); + var payload = Encoding.UTF8.GetBytes(eventData.Payload); + + var headersJson = serializer.Serialize(eventData.Headers); + var headersBytes = Encoding.UTF8.GetBytes(headersJson); - return new EventStoreData(Guid.NewGuid(), eventData.Type, true, body, meta); + return new EventStoreData(Guid.NewGuid(), eventData.Type, true, payload, headersBytes); } } } diff --git a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs index 9ea5bc45c..68ceef876 100644 --- a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs +++ b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStore.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using EventStore.ClientAPI; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Log; namespace Squidex.Infrastructure.EventSourcing @@ -20,14 +21,17 @@ namespace Squidex.Infrastructure.EventSourcing private const int WritePageSize = 500; private const int ReadPageSize = 500; private readonly IEventStoreConnection connection; + private readonly IJsonSerializer serializer; private readonly string prefix; private readonly ProjectionClient projectionClient; - public GetEventStore(IEventStoreConnection connection, string prefix, string projectionHost) + public GetEventStore(IEventStoreConnection connection, IJsonSerializer serializer, string prefix, string projectionHost) { Guard.NotNull(connection, nameof(connection)); + Guard.NotNull(serializer, nameof(serializer)); this.connection = connection; + this.serializer = serializer; this.prefix = prefix?.Trim(' ', '-').WithFallback("squidex"); @@ -50,7 +54,7 @@ namespace Squidex.Infrastructure.EventSourcing public IEventSubscription CreateSubscription(IEventSubscriber subscriber, string streamFilter, string position = null) { - return new GetEventStoreSubscription(connection, subscriber, projectionClient, position, streamFilter); + return new GetEventStoreSubscription(connection, subscriber, serializer, projectionClient, position, streamFilter); } public Task CreateIndexAsync(string property) @@ -95,7 +99,7 @@ namespace Squidex.Infrastructure.EventSourcing foreach (var resolved in currentSlice.Events) { - var storedEvent = Formatter.Read(resolved); + var storedEvent = Formatter.Read(resolved, serializer); await callback(storedEvent); } @@ -123,7 +127,7 @@ namespace Squidex.Infrastructure.EventSourcing foreach (var resolved in currentSlice.Events) { - var storedEvent = Formatter.Read(resolved); + var storedEvent = Formatter.Read(resolved, serializer); result.Add(storedEvent); } @@ -164,7 +168,7 @@ namespace Squidex.Infrastructure.EventSourcing return; } - var eventsToSave = events.Select(Formatter.Write).ToList(); + var eventsToSave = events.Select(x => Formatter.Write(x, serializer)).ToList(); if (eventsToSave.Count < WritePageSize) { diff --git a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs index e77d4a204..a1d3faf01 100644 --- a/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs +++ b/src/Squidex.Infrastructure.GetEventStore/EventSourcing/GetEventStoreSubscription.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using EventStore.ClientAPI; using EventStore.ClientAPI.Exceptions; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Tasks; namespace Squidex.Infrastructure.EventSourcing @@ -16,12 +17,14 @@ namespace Squidex.Infrastructure.EventSourcing { private readonly IEventStoreConnection connection; private readonly IEventSubscriber subscriber; + private readonly IJsonSerializer serializer; private readonly EventStoreCatchUpSubscription subscription; private readonly long? position; public GetEventStoreSubscription( IEventStoreConnection connection, IEventSubscriber subscriber, + IJsonSerializer serializer, ProjectionClient projectionClient, string position, string streamFilter) @@ -34,8 +37,10 @@ namespace Squidex.Infrastructure.EventSourcing var streamName = projectionClient.CreateProjectionAsync(streamFilter).Result; + this.serializer = serializer; this.subscriber = subscriber; - this.subscription = SubscribeToStream(streamName); + + subscription = SubscribeToStream(streamName); } public Task StopAsync() @@ -56,7 +61,7 @@ namespace Squidex.Infrastructure.EventSourcing return connection.SubscribeToStreamFrom(streamName, position, settings, (s, e) => { - var storedEvent = Formatter.Read(e); + var storedEvent = Formatter.Read(e, serializer); subscriber.OnEventAsync(this, storedEvent).Wait(); }, null, diff --git a/src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs b/src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs index 33ed7b077..492c0eae6 100644 --- a/src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs +++ b/src/Squidex.Infrastructure.MongoDb/Diagnostics/MongoDBHealthCheck.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; @@ -15,6 +16,11 @@ namespace Squidex.Infrastructure.Diagnostics { private readonly IMongoDatabase mongoDatabase; + public IEnumerable Scopes + { + get { yield return HealthCheckScopes.Node; } + } + public MongoDBHealthCheck(IMongoDatabase mongoDatabase) { Guard.NotNull(mongoDatabase, nameof(mongoDatabase)); diff --git a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs index 395fa594f..70e4d3397 100644 --- a/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs +++ b/src/Squidex.Infrastructure.MongoDb/EventSourcing/MongoEvent.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -using Newtonsoft.Json.Linq; using Squidex.Infrastructure.MongoDb; namespace Squidex.Infrastructure.EventSourcing @@ -21,18 +21,19 @@ namespace Squidex.Infrastructure.EventSourcing [BsonRequired] public string Payload { get; set; } - [BsonElement] + [BsonElement("Metadata")] [BsonRequired] - public JToken Metadata { get; set; } + [BsonJson] + public EnvelopeHeaders Headers { get; set; } public static MongoEvent FromEventData(EventData data) { - return new MongoEvent { Type = data.Type, Metadata = data.Metadata, Payload = data.Payload.ToString() }; + return new MongoEvent { Type = data.Type, Headers = data.Headers, Payload = data.Payload }; } public EventData ToEventData() { - return new EventData { Type = Type, Metadata = Metadata, Payload = JObject.Parse(Payload) }; + return new EventData(Type, Headers, Payload); } } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConverter.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConverter.cs deleted file mode 100644 index e9aa9d8cc..000000000 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonConverter.cs +++ /dev/null @@ -1,155 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Globalization; -using MongoDB.Bson; -using Newtonsoft.Json.Linq; - -namespace Squidex.Infrastructure.MongoDb -{ - public static class BsonJsonConverter - { - public static BsonDocument ToBson(this JObject source) - { - var result = new BsonDocument(); - - foreach (var property in source) - { - result.Add(property.Key.EscapeJson(), property.Value.ToBson()); - } - - return result; - } - - public static JObject ToJson(this BsonDocument source) - { - var result = new JObject(); - - foreach (var property in source) - { - result.Add(property.Name.UnescapeBson(), property.Value.ToJson()); - } - - return result; - } - - public static BsonArray ToBson(this JArray source) - { - var result = new BsonArray(); - - foreach (var item in source) - { - result.Add(item.ToBson()); - } - - return result; - } - - public static JArray ToJson(this BsonArray source) - { - var result = new JArray(); - - foreach (var item in source) - { - result.Add(item.ToJson()); - } - - return result; - } - - public static BsonValue ToBson(this JToken source) - { - switch (source.Type) - { - case JTokenType.Object: - return ((JObject)source).ToBson(); - case JTokenType.Array: - return ((JArray)source).ToBson(); - case JTokenType.Integer: - return BsonValue.Create(((JValue)source).Value); - case JTokenType.Float: - return BsonValue.Create(((JValue)source).Value); - case JTokenType.String: - return BsonValue.Create(((JValue)source).Value); - case JTokenType.Boolean: - return BsonValue.Create(((JValue)source).Value); - case JTokenType.Null: - return BsonNull.Value; - case JTokenType.Undefined: - return BsonUndefined.Value; - case JTokenType.Bytes: - return BsonValue.Create(((JValue)source).Value); - case JTokenType.Guid: - return BsonValue.Create(((JValue)source).ToString(CultureInfo.InvariantCulture)); - case JTokenType.Uri: - return BsonValue.Create(((JValue)source).ToString(CultureInfo.InvariantCulture)); - case JTokenType.TimeSpan: - return BsonValue.Create(((JValue)source).ToString(CultureInfo.InvariantCulture)); - case JTokenType.Date: - { - var value = ((JValue)source).Value; - - if (value is DateTime dateTime) - { - return dateTime.ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture); - } - else if (value is DateTimeOffset dateTimeOffset) - { - if (dateTimeOffset.Offset == TimeSpan.Zero) - { - return dateTimeOffset.UtcDateTime.ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture); - } - else - { - return dateTimeOffset.ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture); - } - } - else - { - return value.ToString(); - } - } - } - - throw new NotSupportedException($"Cannot convert {source.GetType()} to Bson."); - } - - public static JToken ToJson(this BsonValue source) - { - switch (source.BsonType) - { - case BsonType.Document: - return source.AsBsonDocument.ToJson(); - case BsonType.Array: - return source.AsBsonArray.ToJson(); - case BsonType.Double: - return new JValue(source.AsDouble); - case BsonType.String: - return new JValue(source.AsString); - case BsonType.Boolean: - return new JValue(source.AsBoolean); - case BsonType.DateTime: - return new JValue(source.ToUniversalTime()); - case BsonType.Int32: - return new JValue(source.AsInt32); - case BsonType.Int64: - return new JValue(source.AsInt64); - case BsonType.Decimal128: - return new JValue(source.AsDecimal); - case BsonType.Binary: - return new JValue(source.AsBsonBinaryData.Bytes); - case BsonType.Null: - return JValue.CreateNull(); - case BsonType.Undefined: - return JValue.CreateUndefined(); - } - - throw new NotSupportedException($"Cannot convert {source.GetType()} to Json."); - } - } -} \ No newline at end of file diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonReader.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonReader.cs index acb8708fa..f801aeddc 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonReader.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonReader.cs @@ -37,16 +37,6 @@ namespace Squidex.Infrastructure.MongoDb { SetToken(NewtonsoftJsonToken.PropertyName, bsonReader.ReadName().UnescapeBson()); } - else if (bsonReader.State == BsonReaderState.EndOfDocument) - { - SetToken(NewtonsoftJsonToken.EndObject); - bsonReader.ReadEndDocument(); - } - else if (bsonReader.State == BsonReaderState.EndOfArray) - { - SetToken(NewtonsoftJsonToken.EndArray); - bsonReader.ReadEndArray(); - } else if (bsonReader.State == BsonReaderState.Value) { switch (bsonReader.CurrentBsonType) @@ -95,6 +85,16 @@ namespace Squidex.Infrastructure.MongoDb throw new NotSupportedException(); } } + else if (bsonReader.State == BsonReaderState.EndOfDocument) + { + SetToken(NewtonsoftJsonToken.EndObject); + bsonReader.ReadEndDocument(); + } + else if (bsonReader.State == BsonReaderState.EndOfArray) + { + SetToken(NewtonsoftJsonToken.EndArray); + bsonReader.ReadEndArray(); + } if (bsonReader.State == BsonReaderState.Initial) { diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs index 3681a4aa7..d8baba185 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoExtensions.cs @@ -101,10 +101,7 @@ namespace Squidex.Infrastructure.MongoDb { var update = updater(Builders.Update.Set(x => x.Version, newVersion)); - await collection.UpdateOneAsync(x => x.Id.Equals(key) && x.Version == oldVersion, - update - .Set(x => x.Version, newVersion), - Upsert); + await collection.UpdateOneAsync(x => x.Id.Equals(key) && x.Version == oldVersion, update, Upsert); } catch (MongoWriteException ex) { diff --git a/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs b/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs index aaa0e7c55..3b76605e0 100644 --- a/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs +++ b/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs @@ -26,10 +26,15 @@ namespace Squidex.Infrastructure.MongoDb.Queries if (query.Filter != null) { - return (FilterVisitor.Visit(query.Filter), true); + return (query.Filter.BuildFilter(), true); } return (null, false); } + + public static FilterDefinition BuildFilter(this FilterNode filterNode) + { + return FilterVisitor.Visit(filterNode); + } } } diff --git a/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs b/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs index 238698bda..b378743c1 100644 --- a/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs +++ b/src/Squidex.Infrastructure.RabbitMq/CQRS/Events/RabbitMqEventConsumer.cs @@ -9,16 +9,16 @@ using System; using System.Text; using System.Threading; using System.Threading.Tasks; -using Newtonsoft.Json; using RabbitMQ.Client; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Tasks; namespace Squidex.Infrastructure.CQRS.Events { public sealed class RabbitMqEventConsumer : DisposableObjectBase, IInitializable, IEventConsumer { - private readonly JsonSerializerSettings serializerSettings; + private readonly IJsonSerializer jsonSerializer; private readonly string eventPublisherName; private readonly string exchange; private readonly string eventsFilter; @@ -36,12 +36,12 @@ namespace Squidex.Infrastructure.CQRS.Events get { return eventsFilter; } } - public RabbitMqEventConsumer(JsonSerializerSettings serializerSettings, string eventPublisherName, string uri, string exchange, string eventsFilter) + public RabbitMqEventConsumer(IJsonSerializer jsonSerializer, string eventPublisherName, string uri, string exchange, string eventsFilter) { Guard.NotNullOrEmpty(uri, nameof(uri)); Guard.NotNullOrEmpty(eventPublisherName, nameof(eventPublisherName)); Guard.NotNullOrEmpty(exchange, nameof(exchange)); - Guard.NotNull(serializerSettings, nameof(serializerSettings)); + Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); connectionFactory = new ConnectionFactory { Uri = new Uri(uri, UriKind.Absolute) }; connection = new Lazy(connectionFactory.CreateConnection); @@ -49,8 +49,8 @@ namespace Squidex.Infrastructure.CQRS.Events this.exchange = exchange; this.eventsFilter = eventsFilter; + this.jsonSerializer = jsonSerializer; this.eventPublisherName = eventPublisherName; - this.serializerSettings = serializerSettings; } protected override void DisposeObject(bool disposing) @@ -88,7 +88,7 @@ namespace Squidex.Infrastructure.CQRS.Events public Task On(Envelope @event) { - var jsonString = JsonConvert.SerializeObject(@event, serializerSettings); + var jsonString = jsonSerializer.Serialize(@event); var jsonBytes = Encoding.UTF8.GetBytes(jsonString); channel.Value.BasicPublish(exchange, string.Empty, null, jsonBytes); diff --git a/src/Squidex.Infrastructure.Redis/RedisPubSub.cs b/src/Squidex.Infrastructure.Redis/RedisPubSub.cs index ce5e9f1f0..48cdb58b0 100644 --- a/src/Squidex.Infrastructure.Redis/RedisPubSub.cs +++ b/src/Squidex.Infrastructure.Redis/RedisPubSub.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Tasks; using StackExchange.Redis; @@ -19,11 +20,13 @@ namespace Squidex.Infrastructure { private readonly ConcurrentDictionary subscriptions = new ConcurrentDictionary(); private readonly Lazy redisClient; + private readonly IJsonSerializer serializer; private readonly Lazy redisSubscriber; private readonly ISemanticLog log; - public RedisPubSub(Lazy redis, ISemanticLog log) + public RedisPubSub(Lazy redis, IJsonSerializer serializer, ISemanticLog log) { + Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(redis, nameof(redis)); Guard.NotNull(log, nameof(log)); @@ -31,6 +34,8 @@ namespace Squidex.Infrastructure redisClient = redis; redisSubscriber = new Lazy(() => redis.Value.GetSubscriber()); + + this.serializer = serializer; } public Task InitializeAsync(CancellationToken ct = default(CancellationToken)) @@ -61,7 +66,7 @@ namespace Squidex.Infrastructure { var typeName = typeof(T).FullName; - return (RedisSubscription)subscriptions.GetOrAdd(typeName, this, (k, c) => new RedisSubscription(c.redisSubscriber.Value, k, c.log)); + return (RedisSubscription)subscriptions.GetOrAdd(typeName, this, (k, c) => new RedisSubscription(c.redisSubscriber.Value, serializer, k, c.log)); } } } diff --git a/src/Squidex.Infrastructure.Redis/RedisSubscription.cs b/src/Squidex.Infrastructure.Redis/RedisSubscription.cs index d2e88a555..dc8c10be2 100644 --- a/src/Squidex.Infrastructure.Redis/RedisSubscription.cs +++ b/src/Squidex.Infrastructure.Redis/RedisSubscription.cs @@ -7,7 +7,7 @@ using System; using System.Reactive.Subjects; -using Newtonsoft.Json; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Log; using StackExchange.Redis; @@ -20,6 +20,7 @@ namespace Squidex.Infrastructure private readonly Guid selfId = Guid.NewGuid(); private readonly Subject subject = new Subject(); private readonly ISubscriber subscriber; + private readonly IJsonSerializer serializer; private readonly ISemanticLog log; private readonly string channelName; @@ -30,10 +31,11 @@ namespace Squidex.Infrastructure public Guid Sender; } - public RedisSubscription(ISubscriber subscriber, string channelName, ISemanticLog log) + public RedisSubscription(ISubscriber subscriber, IJsonSerializer serializer, string channelName, ISemanticLog log) { this.log = log; + this.serializer = serializer; this.subscriber = subscriber; this.subscriber.Subscribe(channelName, (channel, value) => HandleMessage(value)); @@ -46,7 +48,7 @@ namespace Squidex.Infrastructure { var senderId = notifySelf ? Guid.Empty : selfId; - var envelope = JsonConvert.SerializeObject(new Envelope { Sender = senderId, Payload = (T)value }); + var envelope = serializer.Serialize(new Envelope { Sender = senderId, Payload = (T)value }); subscriber.Publish(channelName, envelope); } @@ -68,7 +70,7 @@ namespace Squidex.Infrastructure return; } - var envelope = JsonConvert.DeserializeObject(value); + var envelope = serializer.Deserialize(value); if (envelope.Sender != selfId) { diff --git a/src/Squidex.Infrastructure/Assets/AssetFile.cs b/src/Squidex.Infrastructure/Assets/AssetFile.cs index bbaa0917d..4f5ef010f 100644 --- a/src/Squidex.Infrastructure/Assets/AssetFile.cs +++ b/src/Squidex.Infrastructure/Assets/AssetFile.cs @@ -7,7 +7,6 @@ using System; using System.IO; -using Newtonsoft.Json; namespace Squidex.Infrastructure.Assets { @@ -21,7 +20,6 @@ namespace Squidex.Infrastructure.Assets public long FileSize { get; } - [JsonConstructor] public AssetFile(string fileName, string mimeType, long fileSize, Func openAction) { Guard.NotNullOrEmpty(fileName, nameof(fileName)); diff --git a/src/Squidex.Infrastructure/Caching/LRUCache.cs b/src/Squidex.Infrastructure/Caching/LRUCache.cs index 4632d2ab0..7132eb1d3 100644 --- a/src/Squidex.Infrastructure/Caching/LRUCache.cs +++ b/src/Squidex.Infrastructure/Caching/LRUCache.cs @@ -9,23 +9,19 @@ using System.Collections.Generic; namespace Squidex.Infrastructure.Caching { - public sealed class LRUCache + public sealed class LRUCache { - private readonly Dictionary> cacheMap = new Dictionary>(); - private readonly LinkedList cacheHistory = new LinkedList(); + private readonly Dictionary>> cacheMap = new Dictionary>>(); + private readonly LinkedList> cacheHistory = new LinkedList>(); private readonly int capacity; public LRUCache(int capacity) { - Guard.GreaterThan(capacity, 0, nameof(capacity)); - this.capacity = capacity; } - public bool Set(object key, object value) + public bool Set(TKey key, TValue value) { - Guard.NotNull(key, nameof(key)); - if (cacheMap.TryGetValue(key, out var node)) { node.Value.Value = value; @@ -37,28 +33,24 @@ namespace Squidex.Infrastructure.Caching return true; } - else + + if (cacheMap.Count >= capacity) { - if (cacheMap.Count >= capacity) - { - RemoveFirst(); - } + RemoveFirst(); + } - var cacheItem = new LRUCacheItem { Key = key, Value = value }; + var cacheItem = new LRUCacheItem { Key = key, Value = value }; - node = new LinkedListNode(cacheItem); + node = new LinkedListNode>(cacheItem); - cacheMap.Add(key, node); - cacheHistory.AddLast(node); + cacheMap.Add(key, node); + cacheHistory.AddLast(node); - return false; - } + return false; } - public bool Remove(object key) + public bool Remove(TKey key) { - Guard.NotNull(key, nameof(key)); - if (cacheMap.TryGetValue(key, out var node)) { cacheMap.Remove(key); @@ -70,10 +62,8 @@ namespace Squidex.Infrastructure.Caching return false; } - public bool TryGetValue(object key, out object value) + public bool TryGetValue(TKey key, out object value) { - Guard.NotNull(key, nameof(key)); - value = null; if (cacheMap.TryGetValue(key, out var node)) @@ -89,10 +79,8 @@ namespace Squidex.Infrastructure.Caching return false; } - public bool Contains(object key) + public bool Contains(TKey key) { - Guard.NotNull(key, nameof(key)); - return cacheMap.ContainsKey(key); } diff --git a/src/Squidex.Infrastructure/Caching/LRUCacheItem.cs b/src/Squidex.Infrastructure/Caching/LRUCacheItem.cs index ddf885afd..ff9cb3eef 100644 --- a/src/Squidex.Infrastructure/Caching/LRUCacheItem.cs +++ b/src/Squidex.Infrastructure/Caching/LRUCacheItem.cs @@ -9,9 +9,10 @@ namespace Squidex.Infrastructure.Caching { - internal class LRUCacheItem + internal class LRUCacheItem { - public object Key; - public object Value; + public TKey Key; + + public TValue Value; } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure/CollectionExtensions.cs b/src/Squidex.Infrastructure/CollectionExtensions.cs index 61513594d..442cb65a2 100644 --- a/src/Squidex.Infrastructure/CollectionExtensions.cs +++ b/src/Squidex.Infrastructure/CollectionExtensions.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; namespace Squidex.Infrastructure @@ -21,45 +20,40 @@ namespace Squidex.Infrastructure return enumerable.OrderBy(x => random.Next()).ToList(); } - public static ImmutableDictionary SetItem(this ImmutableDictionary dictionary, TKey key, Func updater) + public static IEnumerable OrEmpty(this IEnumerable source) { - if (dictionary.TryGetValue(key, out var value)) - { - var newValue = updater(value); - - if (!Equals(newValue, value)) - { - return dictionary.SetItem(key, newValue); - } - } + return source ?? Enumerable.Empty(); + } - return dictionary; + public static IEnumerable Concat(this IEnumerable source, T value) + { + return source.Concat(Enumerable.Repeat(value, 1)); } - public static bool TryGetValue(this IReadOnlyDictionary values, TKey key, out TBase item) where TValue : TBase + public static TResult[] ToArray(this T[] value, Func convert) { - if (values.TryGetValue(key, out var value)) - { - item = value; + var result = new TResult[value.Length]; - return true; - } - else + for (var i = 0; i < value.Length; i++) { - item = default(TBase); - - return false; + result[i] = convert(value[i]); } - } - public static IEnumerable OrEmpty(this IEnumerable source) - { - return source ?? Enumerable.Empty(); + return result; } - public static IEnumerable Concat(this IEnumerable source, T value) + public static TResult[] ToArray(this IReadOnlyCollection value, Func convert) { - return source.Concat(Enumerable.Repeat(value, 1)); + var result = new TResult[value.Count]; + var i = 0; + + foreach (var v in value) + { + result[i] = convert(v); + i++; + } + + return result; } public static int SequentialHashCode(this IEnumerable collection) diff --git a/src/Squidex.Infrastructure/Collections/ArrayDictionary.cs b/src/Squidex.Infrastructure/Collections/ArrayDictionary.cs new file mode 100644 index 000000000..1a6f1afd2 --- /dev/null +++ b/src/Squidex.Infrastructure/Collections/ArrayDictionary.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Squidex.Infrastructure.Collections +{ + public static class ArrayDictionary + { + public static ArrayDictionary ToArrayDictionary(this IEnumerable source, Func keyExtractor) + { + return new ArrayDictionary(source.Select(x => new KeyValuePair(keyExtractor(x), x)).ToArray()); + } + } +} diff --git a/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs b/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs new file mode 100644 index 000000000..30f1adc90 --- /dev/null +++ b/src/Squidex.Infrastructure/Collections/ArrayDictionary{TKey,TValue}.cs @@ -0,0 +1,163 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Squidex.Infrastructure.Collections +{ + public class ArrayDictionary : IReadOnlyDictionary + { + private readonly IEqualityComparer keyComparer; + private readonly KeyValuePair[] items; + + public TValue this[TKey key] + { + get + { + if (!TryGetValue(key, out var value)) + { + throw new KeyNotFoundException(); + } + + return value; + } + } + + public IEnumerable Keys + { + get { return items.Select(x => x.Key); } + } + + public IEnumerable Values + { + get { return items.Select(x => x.Value); } + } + + public int Count + { + get { return items.Length; } + } + + public ArrayDictionary() + : this(EqualityComparer.Default, Array.Empty>()) + { + } + + public ArrayDictionary(KeyValuePair[] items) + : this(EqualityComparer.Default, items) + { + } + + public ArrayDictionary(IEqualityComparer keyComparer, KeyValuePair[] items) + { + Guard.NotNull(items, nameof(items)); + Guard.NotNull(keyComparer, nameof(keyComparer)); + + this.items = items; + + this.keyComparer = keyComparer; + } + + public KeyValuePair[] With(TKey key, TValue value) + { + var result = new List>(Math.Max(items.Length, 1)); + + var wasReplaced = false; + + for (var i = 0; i < items.Length; i++) + { + var item = items[i]; + + if (wasReplaced || !keyComparer.Equals(item.Key, key)) + { + result.Add(item); + } + else + { + result.Add(new KeyValuePair(key, value)); + wasReplaced = true; + } + } + + if (!wasReplaced) + { + result.Add(new KeyValuePair(key, value)); + } + + return result.ToArray(); + } + + public KeyValuePair[] Without(TKey key) + { + var result = new List>(Math.Max(items.Length, 1)); + + var wasRemoved = false; + + for (var i = 0; i < items.Length; i++) + { + var item = items[i]; + + if (wasRemoved || !keyComparer.Equals(item.Key, key)) + { + result.Add(item); + } + else + { + wasRemoved = true; + } + } + + return result.ToArray(); + } + + public bool ContainsKey(TKey key) + { + for (var i = 0; i < items.Length; i++) + { + if (keyComparer.Equals(items[i].Key, key)) + { + return true; + } + } + + return false; + } + + public bool TryGetValue(TKey key, out TValue value) + { + for (var i = 0; i < items.Length; i++) + { + if (keyComparer.Equals(items[i].Key, key)) + { + value = items[i].Value; + return true; + } + } + + value = default(TValue); + return false; + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetEnumerable(items).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return items.GetEnumerator(); + } + + private static IEnumerable GetEnumerable(T2[] array) + { + return array; + } + } +} diff --git a/src/Squidex.Infrastructure/Collections/ReadOnlyCollection.cs b/src/Squidex.Infrastructure/Collections/ReadOnlyCollection.cs new file mode 100644 index 000000000..83466a3a0 --- /dev/null +++ b/src/Squidex.Infrastructure/Collections/ReadOnlyCollection.cs @@ -0,0 +1,36 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Squidex.Infrastructure.Collections +{ + public static class ReadOnlyCollection + { + private static class Empties + { + public static readonly ReadOnlyCollection Collection = new ReadOnlyCollection(new List()); + } + + public static ReadOnlyCollection Create(params T[] items) + { + return new ReadOnlyCollection(items.ToList()); + } + + public static ReadOnlyCollection Empty() + { + return Empties.Collection; + } + + public static ReadOnlyCollection ToReadOnlyCollection(this IEnumerable source) + { + return new ReadOnlyCollection(source.ToList()); + } + } +} diff --git a/src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs b/src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs index 1f6044d3d..548a82c33 100644 --- a/src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs +++ b/src/Squidex.Infrastructure/Commands/DomainObjectGrainBase.cs @@ -221,7 +221,7 @@ namespace Squidex.Infrastructure.Commands { var result = await ExecuteAsync(command.Value); - return result.AsJ(); + return result; } protected abstract Task ExecuteAsync(IAggregateCommand command); diff --git a/src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs b/src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs index 62d175e74..0a9b4aac7 100644 --- a/src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs +++ b/src/Squidex.Infrastructure/Diagnostics/GCHealthCheck.cs @@ -17,6 +17,11 @@ namespace Squidex.Infrastructure.Diagnostics { private readonly long threshold; + public IEnumerable Scopes + { + get { yield return HealthCheckScopes.Node; } + } + public GCHealthCheck(IOptions options) { Guard.NotNull(options, nameof(options)); diff --git a/src/Squidex.Infrastructure/Diagnostics/HealthCheckScopes.cs b/src/Squidex.Infrastructure/Diagnostics/HealthCheckScopes.cs new file mode 100644 index 000000000..327c46c22 --- /dev/null +++ b/src/Squidex.Infrastructure/Diagnostics/HealthCheckScopes.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Diagnostics +{ + public static class HealthCheckScopes + { + public const string Any = "*"; + public const string Node = "node"; + public const string Cluster = "cluster"; + } +} diff --git a/src/Squidex.Infrastructure/Diagnostics/IHealthCheck.cs b/src/Squidex.Infrastructure/Diagnostics/IHealthCheck.cs index f139deaed..eaf8e11b0 100644 --- a/src/Squidex.Infrastructure/Diagnostics/IHealthCheck.cs +++ b/src/Squidex.Infrastructure/Diagnostics/IHealthCheck.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -12,6 +13,8 @@ namespace Squidex.Infrastructure.Diagnostics { public interface IHealthCheck { + IEnumerable Scopes { get; } + Task CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs b/src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs index 8c6a26f1a..8351626d9 100644 --- a/src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs +++ b/src/Squidex.Infrastructure/Diagnostics/OrleansHealthCheck.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Orleans; @@ -16,6 +17,11 @@ namespace Squidex.Infrastructure.Diagnostics { private readonly IManagementGrain managementGrain; + public IEnumerable Scopes + { + get { yield return HealthCheckScopes.Cluster; } + } + public OrleansHealthCheck(IGrainFactory grainFactory) { Guard.NotNull(grainFactory, nameof(grainFactory)); diff --git a/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs b/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs index 98b7919b1..8135566cf 100644 --- a/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs +++ b/src/Squidex.Infrastructure/EventSourcing/DefaultEventDataFormatter.cs @@ -6,38 +6,36 @@ // ========================================================================== using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using Squidex.Infrastructure.Json; namespace Squidex.Infrastructure.EventSourcing { - public class DefaultEventDataFormatter : IEventDataFormatter + public sealed class DefaultEventDataFormatter : IEventDataFormatter { - private readonly JsonSerializer serializer; + private readonly IJsonSerializer serializer; private readonly TypeNameRegistry typeNameRegistry; - public DefaultEventDataFormatter(TypeNameRegistry typeNameRegistry, JsonSerializer serializer = null) + public DefaultEventDataFormatter(TypeNameRegistry typeNameRegistry, IJsonSerializer serializer) { Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); + Guard.NotNull(serializer, nameof(serializer)); this.typeNameRegistry = typeNameRegistry; - this.serializer = serializer ?? JsonSerializer.CreateDefault(); + this.serializer = serializer; } - public Envelope Parse(EventData eventData, bool migrate = true) + public Envelope Parse(EventData eventData, Func stringConverter = null) { - var eventType = typeNameRegistry.GetType(eventData.Type); + var payloadType = typeNameRegistry.GetType(eventData.Type); + var payloadObj = serializer.Deserialize(eventData.Payload, payloadType, stringConverter); - var headers = eventData.Metadata.ToObject(serializer); - var content = eventData.Payload.ToObject(eventType, serializer) as IEvent; - - if (migrate && content is IMigratedEvent migratedEvent) + if (payloadObj is IMigratedEvent migratedEvent) { - content = migratedEvent.Migrate(); + payloadObj = migratedEvent.Migrate(); } - var envelope = new Envelope(content, headers); + var envelope = new Envelope(payloadObj, eventData.Headers); return envelope; } @@ -51,14 +49,12 @@ namespace Squidex.Infrastructure.EventSourcing eventPayload = migratedEvent.Migrate(); } - var eventType = typeNameRegistry.GetName(eventPayload.GetType()); + var payloadType = typeNameRegistry.GetName(eventPayload.GetType()); + var payloadJson = serializer.Serialize(envelope.Payload); envelope.SetCommitId(commitId); - var headers = JToken.FromObject(envelope.Headers, serializer); - var content = JToken.FromObject(envelope.Payload, serializer); - - return new EventData { Type = eventType, Payload = content, Metadata = headers }; + return new EventData(payloadType, envelope.Headers, payloadJson); } } } diff --git a/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs b/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs index a1d60034c..e08a6ec4f 100644 --- a/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs +++ b/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs @@ -8,6 +8,8 @@ using System; using System.Globalization; using NodaTime; +using NodaTime.Text; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Infrastructure.EventSourcing { @@ -15,74 +17,127 @@ namespace Squidex.Infrastructure.EventSourcing { public static string EventPosition(this EnvelopeHeaders headers) { - return headers[CommonHeaders.EventNumber].ToString(); + return headers.GetString(CommonHeaders.EventNumber); } public static Envelope SetEventPosition(this Envelope envelope, string value) where T : class { - envelope.Headers.Set(CommonHeaders.EventNumber, value); + envelope.Headers.Add(CommonHeaders.EventNumber, value); return envelope; } public static long EventStreamNumber(this EnvelopeHeaders headers) { - return headers[CommonHeaders.EventStreamNumber].ToInt64(CultureInfo.InvariantCulture); + return headers.GetLong(CommonHeaders.EventStreamNumber); } public static Envelope SetEventStreamNumber(this Envelope envelope, long value) where T : class { - envelope.Headers.Set(CommonHeaders.EventStreamNumber, value); + envelope.Headers.Add(CommonHeaders.EventStreamNumber, value); return envelope; } public static Guid CommitId(this EnvelopeHeaders headers) { - return headers[CommonHeaders.CommitId].ToGuid(CultureInfo.InvariantCulture); + return headers.GetGuid(CommonHeaders.CommitId); } public static Envelope SetCommitId(this Envelope envelope, Guid value) where T : class { - envelope.Headers.Set(CommonHeaders.CommitId, value); + envelope.Headers.Add(CommonHeaders.CommitId, value.ToString()); return envelope; } public static Guid AggregateId(this EnvelopeHeaders headers) { - return headers[CommonHeaders.AggregateId].ToGuid(CultureInfo.InvariantCulture); + return headers.GetGuid(CommonHeaders.AggregateId); } public static Envelope SetAggregateId(this Envelope envelope, Guid value) where T : class { - envelope.Headers.Set(CommonHeaders.AggregateId, value); + envelope.Headers.Add(CommonHeaders.AggregateId, value.ToString()); return envelope; } public static Guid EventId(this EnvelopeHeaders headers) { - return headers[CommonHeaders.EventId].ToGuid(CultureInfo.InvariantCulture); + return headers.GetGuid(CommonHeaders.EventId); } public static Envelope SetEventId(this Envelope envelope, Guid value) where T : class { - envelope.Headers.Set(CommonHeaders.EventId, value); + envelope.Headers.Add(CommonHeaders.EventId, value.ToString()); return envelope; } public static Instant Timestamp(this EnvelopeHeaders headers) { - return headers[CommonHeaders.Timestamp].ToInstant(CultureInfo.InvariantCulture); + return headers.GetInstant(CommonHeaders.Timestamp); } public static Envelope SetTimestamp(this Envelope envelope, Instant value) where T : class { - envelope.Headers.Set(CommonHeaders.Timestamp, value); + envelope.Headers.Add(CommonHeaders.Timestamp, value.ToString()); return envelope; } + + public static long GetLong(this JsonObject obj, string key) + { + if (obj.TryGetValue(key, out var v)) + { + if (v is JsonNumber number) + { + return (long)number.Value; + } + else if (v.Type == JsonValueType.String && double.TryParse(v.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + { + return (long)result; + } + } + + return 0; + } + + public static Guid GetGuid(this JsonObject obj, string key) + { + if (obj.TryGetValue(key, out var v)) + { + if (v.Type == JsonValueType.String && Guid.TryParse(v.ToString(), out var guid)) + { + return guid; + } + } + + return default(Guid); + } + + public static Instant GetInstant(this JsonObject obj, string key) + { + if (obj.TryGetValue(key, out var v)) + { + if (v.Type == JsonValueType.String && InstantPattern.General.Parse(v.ToString()).TryGetValue(default(Instant), out var instant)) + { + return instant; + } + } + + return default(Instant); + } + + public static string GetString(this JsonObject obj, string key) + { + if (obj.TryGetValue(key, out var v)) + { + return v.ToString(); + } + + return string.Empty; + } } } diff --git a/src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs b/src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs index 3b53fcd30..021ec109f 100644 --- a/src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs +++ b/src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs @@ -5,37 +5,24 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Infrastructure.Json.Objects; + namespace Squidex.Infrastructure.EventSourcing { - public sealed class EnvelopeHeaders : PropertiesBag + public sealed class EnvelopeHeaders : JsonObject { public EnvelopeHeaders() { } - public EnvelopeHeaders(PropertiesBag bag) + public EnvelopeHeaders(JsonObject headers) + : base(headers) { - if (bag == null) - { - return; - } - - foreach (var property in bag.Properties) - { - Set(property.Key, property.Value.RawValue); - } } public EnvelopeHeaders Clone() { - var clone = new EnvelopeHeaders(); - - foreach (var property in Properties) - { - clone.Set(property.Key, property.Value.RawValue); - } - - return clone; + return new EnvelopeHeaders(this); } } } diff --git a/src/Squidex.Infrastructure/EventSourcing/EventData.cs b/src/Squidex.Infrastructure/EventSourcing/EventData.cs index 739ea8068..016043919 100644 --- a/src/Squidex.Infrastructure/EventSourcing/EventData.cs +++ b/src/Squidex.Infrastructure/EventSourcing/EventData.cs @@ -5,16 +5,27 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json.Linq; - namespace Squidex.Infrastructure.EventSourcing { - public class EventData + public sealed class EventData { - public JToken Payload { get; set; } + public EnvelopeHeaders Headers { get; } - public JToken Metadata { get; set; } + public string Payload { get; } public string Type { get; set; } + + public EventData(string type, EnvelopeHeaders headers, string payload) + { + Guard.NotNull(type, nameof(type)); + Guard.NotNull(headers, nameof(headers)); + Guard.NotNull(payload, nameof(payload)); + + Headers = headers; + + Payload = payload; + + Type = type; + } } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure/EventSourcing/IEventDataFormatter.cs b/src/Squidex.Infrastructure/EventSourcing/IEventDataFormatter.cs index 6cd586c84..db3c3fdff 100644 --- a/src/Squidex.Infrastructure/EventSourcing/IEventDataFormatter.cs +++ b/src/Squidex.Infrastructure/EventSourcing/IEventDataFormatter.cs @@ -11,7 +11,7 @@ namespace Squidex.Infrastructure.EventSourcing { public interface IEventDataFormatter { - Envelope Parse(EventData eventData, bool migrate = true); + Envelope Parse(EventData eventData, Func stringConverter = null); EventData ToEventData(Envelope envelope, Guid commitId, bool migrate = true); } diff --git a/src/Squidex.Infrastructure/Json/IJsonSerializer.cs b/src/Squidex.Infrastructure/Json/IJsonSerializer.cs new file mode 100644 index 000000000..704ebaa78 --- /dev/null +++ b/src/Squidex.Infrastructure/Json/IJsonSerializer.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.IO; + +namespace Squidex.Infrastructure.Json +{ + public interface IJsonSerializer + { + string Serialize(T value, bool intented = false); + + void Serialize(T value, Stream stream); + + T Deserialize(string value, Type actualType = null, Func stringConverter = null); + + T Deserialize(Stream stream, Type actualType = null, Func stringConverter = null); + } +} diff --git a/src/Squidex.Infrastructure/Json/JsonExtension.cs b/src/Squidex.Infrastructure/Json/JsonExtension.cs deleted file mode 100644 index 23d8cea4c..000000000 --- a/src/Squidex.Infrastructure/Json/JsonExtension.cs +++ /dev/null @@ -1,34 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Newtonsoft.Json.Linq; - -namespace Squidex.Infrastructure.Json -{ - public static class JsonExtension - { - public static bool IsNull(this JToken token) - { - if (token == null) - { - return true; - } - - if (token.Type == JTokenType.Null) - { - return true; - } - - if (token is JValue value) - { - return value.Value == null; - } - - return false; - } - } -} diff --git a/src/Squidex.Infrastructure/Json/ClaimsPrincipalConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/ClaimsPrincipalConverter.cs similarity index 97% rename from src/Squidex.Infrastructure/Json/ClaimsPrincipalConverter.cs rename to src/Squidex.Infrastructure/Json/Newtonsoft/ClaimsPrincipalConverter.cs index c183e6424..bde9e0013 100644 --- a/src/Squidex.Infrastructure/Json/ClaimsPrincipalConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/ClaimsPrincipalConverter.cs @@ -10,7 +10,7 @@ using System.Linq; using System.Security.Claims; using Newtonsoft.Json; -namespace Squidex.Infrastructure.Json +namespace Squidex.Infrastructure.Json.Newtonsoft { public sealed class ClaimsPrincipalConverter : JsonClassConverter { diff --git a/src/Squidex.Infrastructure/Json/ConverterContractResolver.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/ConverterContractResolver.cs similarity index 51% rename from src/Squidex.Infrastructure/Json/ConverterContractResolver.cs rename to src/Squidex.Infrastructure/Json/Newtonsoft/ConverterContractResolver.cs index 55609ed0d..53b203da3 100644 --- a/src/Squidex.Infrastructure/Json/ConverterContractResolver.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/ConverterContractResolver.cs @@ -6,18 +6,32 @@ // ========================================================================== using System; +using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -namespace Squidex.Infrastructure.Json +namespace Squidex.Infrastructure.Json.Newtonsoft { public sealed class ConverterContractResolver : CamelCasePropertyNamesContractResolver { private readonly JsonConverter[] converters; + private readonly object lockObject = new object(); + private Dictionary converterCache = new Dictionary(); public ConverterContractResolver(params JsonConverter[] converters) { this.converters = converters; + + foreach (var converter in converters) + { + if (converter is ISupportedTypes supportedTypes) + { + foreach (var type in supportedTypes.SupportedTypes) + { + converterCache[type] = converter; + } + } + } } protected override JsonDictionaryContract CreateDictionaryContract(Type objectType) @@ -38,15 +52,32 @@ namespace Squidex.Infrastructure.Json return result; } - foreach (var converter in converters) + var cache = converterCache; + + if (cache == null || !cache.TryGetValue(objectType, out result)) { - if (converter.CanConvert(objectType)) + foreach (var converter in converters) + { + if (converter.CanConvert(objectType)) + { + result = converter; + } + } + + lock (lockObject) { - return converter; + cache = converterCache; + + var updatedCache = (cache != null) + ? new Dictionary(cache) + : new Dictionary(); + updatedCache[objectType] = result; + + converterCache = updatedCache; } } - return null; + return result; } } } diff --git a/src/Squidex.Infrastructure/Json/Newtonsoft/EnvelopeHeadersConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/EnvelopeHeadersConverter.cs new file mode 100644 index 000000000..d4a529395 --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/EnvelopeHeadersConverter.cs @@ -0,0 +1,40 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json.Objects; + +namespace Squidex.Infrastructure.Json.Newtonsoft +{ + public sealed class EnvelopeHeadersConverter : JsonValueConverter + { + public override IEnumerable SupportedTypes + { + get { yield return typeof(EnvelopeHeaders); } + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var result = base.ReadJson(reader, objectType, existingValue, serializer); + + if (result is JsonObject obj) + { + return new EnvelopeHeaders(obj); + } + + return result; + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(EnvelopeHeaders); + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Newtonsoft/ISupportedTypes.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/ISupportedTypes.cs new file mode 100644 index 000000000..fa28fb93c --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/ISupportedTypes.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; + +namespace Squidex.Infrastructure.Json.Newtonsoft +{ + public interface ISupportedTypes + { + IEnumerable SupportedTypes { get; } + } +} diff --git a/src/Squidex.Infrastructure/Json/InstantConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/InstantConverter.cs similarity index 85% rename from src/Squidex.Infrastructure/Json/InstantConverter.cs rename to src/Squidex.Infrastructure/Json/Newtonsoft/InstantConverter.cs index deca1ee95..9663accf9 100644 --- a/src/Squidex.Infrastructure/Json/InstantConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/InstantConverter.cs @@ -6,14 +6,24 @@ // ========================================================================== using System; +using System.Collections.Generic; using Newtonsoft.Json; using NodaTime; using NodaTime.Text; -namespace Squidex.Infrastructure.Json +namespace Squidex.Infrastructure.Json.Newtonsoft { public sealed class InstantConverter : JsonConverter { + public IEnumerable SupportedTypes + { + get + { + yield return typeof(Instant); + yield return typeof(Instant?); + } + } + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value != null) diff --git a/src/Squidex.Infrastructure/Json/JsonClassConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/JsonClassConverter.cs similarity index 82% rename from src/Squidex.Infrastructure/Json/JsonClassConverter.cs rename to src/Squidex.Infrastructure/Json/Newtonsoft/JsonClassConverter.cs index 954e472dd..03acfc2f5 100644 --- a/src/Squidex.Infrastructure/Json/JsonClassConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/JsonClassConverter.cs @@ -6,12 +6,18 @@ // ========================================================================== using System; +using System.Collections.Generic; using Newtonsoft.Json; -namespace Squidex.Infrastructure.Json +namespace Squidex.Infrastructure.Json.Newtonsoft { - public abstract class JsonClassConverter : JsonConverter where T : class + public abstract class JsonClassConverter : JsonConverter, ISupportedTypes where T : class { + public IEnumerable SupportedTypes + { + get { yield return typeof(T); } + } + public sealed override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) @@ -22,6 +28,8 @@ namespace Squidex.Infrastructure.Json return ReadValue(reader, objectType, serializer); } + protected abstract T ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer); + public sealed override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) @@ -33,13 +41,11 @@ namespace Squidex.Infrastructure.Json WriteValue(writer, (T)value, serializer); } + protected abstract void WriteValue(JsonWriter writer, T value, JsonSerializer serializer); + public override bool CanConvert(Type objectType) { return objectType == typeof(T); } - - protected abstract void WriteValue(JsonWriter writer, T value, JsonSerializer serializer); - - protected abstract T ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer); } } diff --git a/src/Squidex.Infrastructure/Json/Newtonsoft/JsonValueConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/JsonValueConverter.cs new file mode 100644 index 000000000..0e4282826 --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/JsonValueConverter.cs @@ -0,0 +1,184 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Globalization; +using Newtonsoft.Json; +using Squidex.Infrastructure.Json.Objects; + +#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator + +namespace Squidex.Infrastructure.Json.Newtonsoft +{ + public class JsonValueConverter : JsonConverter, ISupportedTypes + { + private readonly HashSet supportedTypes = new HashSet + { + typeof(IJsonValue), + typeof(JsonArray), + typeof(JsonBoolean), + typeof(JsonNull), + typeof(JsonNumber), + typeof(JsonObject), + typeof(JsonString) + }; + + public virtual IEnumerable SupportedTypes + { + get { return supportedTypes; } + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return ReadJson(reader); + } + + private IJsonValue ReadJson(JsonReader reader) + { + switch (reader.TokenType) + { + case JsonToken.Comment: + reader.Read(); + break; + case JsonToken.StartObject: + { + var result = JsonValue.Object(); + + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + var propertyName = reader.Value.ToString(); + + if (!reader.Read()) + { + throw new JsonSerializationException("Unexpected end when reading Object."); + } + + var value = ReadJson(reader); + + result[propertyName] = value; + break; + case JsonToken.EndObject: + return result; + } + } + + throw new JsonSerializationException("Unexpected end when reading Object."); + } + + case JsonToken.StartArray: + { + var result = JsonValue.Array(); + + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.Comment: + break; + default: + var value = ReadJson(reader); + + result.Add(value); + break; + case JsonToken.EndArray: + return result; + } + } + + throw new JsonSerializationException("Unexpected end when reading Object."); + } + + case JsonToken.Integer: + return JsonValue.Create((long)reader.Value); + case JsonToken.Float: + return JsonValue.Create((double)reader.Value); + case JsonToken.Boolean: + return JsonValue.Create((bool)reader.Value); + case JsonToken.Date: + return JsonValue.Create(((DateTime)reader.Value).ToString("yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture)); + case JsonToken.String: + return JsonValue.Create(reader.Value.ToString()); + case JsonToken.Null: + case JsonToken.Undefined: + return JsonValue.Null; + } + + throw new NotSupportedException(); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteNull(); + return; + } + + WriteJson(writer, (IJsonValue)value); + } + + private void WriteJson(JsonWriter writer, IJsonValue value) + { + switch (value) + { + case JsonNull n: + writer.WriteNull(); + break; + case JsonBoolean s: + writer.WriteValue(s.Value); + break; + case JsonString s: + writer.WriteValue(s.Value); + break; + case JsonNumber s: + + if (s.Value % 1 == 0) + { + writer.WriteValue((long)s.Value); + } + else + { + writer.WriteValue(s.Value); + } + + break; + case JsonArray array: + writer.WriteStartArray(); + + for (var i = 0; i < array.Count; i++) + { + WriteJson(writer, array[i]); + } + + writer.WriteEndArray(); + break; + + case JsonObject obj: + writer.WriteStartObject(); + + foreach (var kvp in obj) + { + writer.WritePropertyName(kvp.Key); + + WriteJson(writer, kvp.Value); + } + + writer.WriteEndObject(); + break; + } + } + + public override bool CanConvert(Type objectType) + { + return supportedTypes.Contains(objectType); + } + } +} diff --git a/src/Squidex.Infrastructure/Json/LanguageConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs similarity index 95% rename from src/Squidex.Infrastructure/Json/LanguageConverter.cs rename to src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs index c2c11ac78..11d45a037 100644 --- a/src/Squidex.Infrastructure/Json/LanguageConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs @@ -8,7 +8,7 @@ using System; using Newtonsoft.Json; -namespace Squidex.Infrastructure.Json +namespace Squidex.Infrastructure.Json.Newtonsoft { public sealed class LanguageConverter : JsonClassConverter { diff --git a/src/Squidex.Infrastructure/Json/NamedGuidIdConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs similarity index 75% rename from src/Squidex.Infrastructure/Json/NamedGuidIdConverter.cs rename to src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs index 1695a96f9..f1b141060 100644 --- a/src/Squidex.Infrastructure/Json/NamedGuidIdConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs @@ -8,13 +8,13 @@ using System; using Newtonsoft.Json; -namespace Squidex.Infrastructure.Json +namespace Squidex.Infrastructure.Json.Newtonsoft { public sealed class NamedGuidIdConverter : JsonClassConverter> { protected override void WriteValue(JsonWriter writer, NamedId value, JsonSerializer serializer) { - writer.WriteValue($"{value.Id},{value.Name}"); + writer.WriteValue(value.ToString()); } protected override NamedId ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) @@ -24,14 +24,12 @@ namespace Squidex.Infrastructure.Json throw new JsonException($"Expected String, but got {reader.TokenType}."); } - try + if (!NamedId.TryParse(reader.Value.ToString(), Guid.TryParse, out var result)) { - return NamedId.Parse(reader.Value.ToString(), Guid.TryParse); - } - catch (ArgumentException ex) - { - throw new JsonException(ex.Message); + throw new JsonException("Named id must have more than 2 parts divided by commata."); } + + return result; } } } diff --git a/src/Squidex.Infrastructure/Json/NamedLongIdConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs similarity index 75% rename from src/Squidex.Infrastructure/Json/NamedLongIdConverter.cs rename to src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs index cc85acd9b..d984e572b 100644 --- a/src/Squidex.Infrastructure/Json/NamedLongIdConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs @@ -8,13 +8,13 @@ using System; using Newtonsoft.Json; -namespace Squidex.Infrastructure.Json +namespace Squidex.Infrastructure.Json.Newtonsoft { public sealed class NamedLongIdConverter : JsonClassConverter> { protected override void WriteValue(JsonWriter writer, NamedId value, JsonSerializer serializer) { - writer.WriteValue($"{value.Id},{value.Name}"); + writer.WriteValue(value.ToString()); } protected override NamedId ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) @@ -24,14 +24,12 @@ namespace Squidex.Infrastructure.Json throw new JsonException($"Expected String, but got {reader.TokenType}."); } - try + if (!NamedId.TryParse(reader.Value.ToString(), long.TryParse, out var result)) { - return NamedId.Parse(reader.Value.ToString(), long.TryParse); - } - catch (ArgumentException ex) - { - throw new JsonException(ex.Message); + throw new JsonException("Named id must have at least 2 parts divided by commata."); } + + return result; } } } diff --git a/src/Squidex.Infrastructure/Json/NamedStringIdConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs similarity index 67% rename from src/Squidex.Infrastructure/Json/NamedStringIdConverter.cs rename to src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs index 3076ef02c..573cbaa57 100644 --- a/src/Squidex.Infrastructure/Json/NamedStringIdConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs @@ -6,16 +6,15 @@ // ========================================================================== using System; -using System.Linq; using Newtonsoft.Json; -namespace Squidex.Infrastructure.Json +namespace Squidex.Infrastructure.Json.Newtonsoft { public sealed class NamedStringIdConverter : JsonClassConverter> { protected override void WriteValue(JsonWriter writer, NamedId value, JsonSerializer serializer) { - writer.WriteValue($"{value.Id},{value.Name}"); + writer.WriteValue(value.ToString()); } protected override NamedId ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) @@ -25,14 +24,19 @@ namespace Squidex.Infrastructure.Json throw new JsonException($"Expected String, but got {reader.TokenType}."); } - var parts = reader.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length < 2) + if (!NamedId.TryParse(reader.Value.ToString(), ParseString, out var result)) { - throw new JsonException("Named id must have more than 2 parts divided by colon."); + throw new JsonException("Named id must have at least 2 parts divided by commata."); } - return NamedId.Of(parts[0], string.Join(",", parts.Skip(1))); + return result; + } + + private static bool ParseString(string value, out string result) + { + result = value; + + return true; } } } diff --git a/src/Squidex.Infrastructure/Json/Newtonsoft/NewtonsoftJsonSerializer.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/NewtonsoftJsonSerializer.cs new file mode 100644 index 000000000..3a79cd5d5 --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/NewtonsoftJsonSerializer.cs @@ -0,0 +1,100 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.IO; +using Newtonsoft.Json; + +namespace Squidex.Infrastructure.Json.Newtonsoft +{ + public sealed class NewtonsoftJsonSerializer : IJsonSerializer + { + private readonly JsonSerializerSettings settings; + private readonly JsonSerializer serializer; + + private sealed class CustomReader : JsonTextReader + { + private readonly Func stringConverter; + + public override object Value + { + get + { + var value = base.Value; + + if (value is string s) + { + return stringConverter(s); + } + + return value; + } + } + + public CustomReader(TextReader reader, Func stringConverter) + : base(reader) + { + this.stringConverter = stringConverter; + } + } + + public NewtonsoftJsonSerializer(JsonSerializerSettings settings) + { + Guard.NotNull(settings, nameof(settings)); + + this.settings = settings; + + serializer = JsonSerializer.Create(settings); + } + + public string Serialize(T value, bool intented) + { + return JsonConvert.SerializeObject(value, intented ? Formatting.Indented : Formatting.None, settings); + } + + public void Serialize(T value, Stream stream) + { + using (var writer = new StreamWriter(stream)) + { + serializer.Serialize(writer, value); + + writer.Flush(); + } + } + + public T Deserialize(string value, Type actualType = null, Func stringConverter = null) + { + using (var textReader = new StringReader(value)) + { + actualType = actualType ?? typeof(T); + + using (var reader = GetReader(stringConverter, textReader)) + { + return (T)serializer.Deserialize(reader, actualType); + } + } + } + + public T Deserialize(Stream stream, Type actualType = null, Func stringConverter = null) + { + using (var textReader = new StreamReader(stream)) + { + actualType = actualType ?? typeof(T); + + using (var reader = GetReader(stringConverter, textReader)) + { + return (T)serializer.Deserialize(reader, actualType); + } + } + } + + private static JsonTextReader GetReader(Func stringConverter, TextReader textReader) + { + return stringConverter != null ? new CustomReader(textReader, stringConverter) : new JsonTextReader(textReader); + } + } +} diff --git a/src/Squidex.Infrastructure/Json/RefTokenConverter.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs similarity index 77% rename from src/Squidex.Infrastructure/Json/RefTokenConverter.cs rename to src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs index 1e2576faa..988f3ff95 100644 --- a/src/Squidex.Infrastructure/Json/RefTokenConverter.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs @@ -8,7 +8,7 @@ using System; using Newtonsoft.Json; -namespace Squidex.Infrastructure.Json +namespace Squidex.Infrastructure.Json.Newtonsoft { public sealed class RefTokenConverter : JsonClassConverter { @@ -24,7 +24,12 @@ namespace Squidex.Infrastructure.Json throw new JsonException($"Expected String, but got {reader.TokenType}."); } - return RefToken.Parse(reader.Value.ToString()); + if (!RefToken.TryParse(reader.Value.ToString(), out var result)) + { + throw new JsonException("Named id must have at least 2 parts divided by colon."); + } + + return result; } } } \ No newline at end of file diff --git a/src/Squidex.Infrastructure/Json/TypeNameSerializationBinder.cs b/src/Squidex.Infrastructure/Json/Newtonsoft/TypeNameSerializationBinder.cs similarity index 96% rename from src/Squidex.Infrastructure/Json/TypeNameSerializationBinder.cs rename to src/Squidex.Infrastructure/Json/Newtonsoft/TypeNameSerializationBinder.cs index 303bd51a0..8e5d41dc2 100644 --- a/src/Squidex.Infrastructure/Json/TypeNameSerializationBinder.cs +++ b/src/Squidex.Infrastructure/Json/Newtonsoft/TypeNameSerializationBinder.cs @@ -8,7 +8,7 @@ using System; using Newtonsoft.Json.Serialization; -namespace Squidex.Infrastructure.Json +namespace Squidex.Infrastructure.Json.Newtonsoft { public class TypeNameSerializationBinder : DefaultSerializationBinder { diff --git a/src/Squidex.Infrastructure/Json/Objects/IJsonValue.cs b/src/Squidex.Infrastructure/Json/Objects/IJsonValue.cs new file mode 100644 index 000000000..743cf4a2a --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Objects/IJsonValue.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; + +namespace Squidex.Infrastructure.Json.Objects +{ + public interface IJsonValue : IEquatable + { + JsonValueType Type { get; } + + string ToJsonString(); + } +} diff --git a/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs b/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs new file mode 100644 index 000000000..e3d58f511 --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Objects/JsonArray.cs @@ -0,0 +1,90 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Squidex.Infrastructure.Json.Objects +{ + public sealed class JsonArray : Collection, IJsonValue, IEquatable + { + public JsonValueType Type + { + get { return JsonValueType.Array; } + } + + public JsonArray() + { + } + + internal JsonArray(params object[] values) + : base(values?.Select(JsonValue.Create).ToList()) + { + } + + protected override void InsertItem(int index, IJsonValue item) + { + base.InsertItem(index, item ?? JsonValue.Null); + } + + protected override void SetItem(int index, IJsonValue item) + { + base.SetItem(index, item ?? JsonValue.Null); + } + + public override bool Equals(object obj) + { + return Equals(obj as JsonArray); + } + + public bool Equals(IJsonValue other) + { + return Equals(other as JsonArray); + } + + public bool Equals(JsonArray array) + { + if (array == null || array.Count != Count) + { + return false; + } + + for (var i = 0; i < Count; i++) + { + if (!this[i].Equals(array[i])) + { + return false; + } + } + + return true; + } + + public override int GetHashCode() + { + var hashCode = 17; + + for (var i = 0; i < Count; i++) + { + hashCode = (hashCode * 23) + this[i].GetHashCode(); + } + + return hashCode; + } + + public string ToJsonString() + { + return ToString(); + } + + public override string ToString() + { + return $"[{string.Join(", ", this.Select(x => x.ToJsonString()))}]"; + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Objects/JsonBoolean.cs b/src/Squidex.Infrastructure/Json/Objects/JsonBoolean.cs new file mode 100644 index 000000000..de4f1ddf0 --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Objects/JsonBoolean.cs @@ -0,0 +1,30 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Json.Objects +{ + public sealed class JsonBoolean : JsonScalar + { + public static readonly JsonBoolean True = new JsonBoolean(true); + public static readonly JsonBoolean False = new JsonBoolean(false); + + public override JsonValueType Type + { + get { return JsonValueType.Boolean; } + } + + private JsonBoolean(bool value) + : base(value) + { + } + + public override string ToString() + { + return Value ? "true" : "false"; + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Objects/JsonNull.cs b/src/Squidex.Infrastructure/Json/Objects/JsonNull.cs new file mode 100644 index 000000000..884462b3c --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Objects/JsonNull.cs @@ -0,0 +1,55 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; + +namespace Squidex.Infrastructure.Json.Objects +{ + public sealed class JsonNull : IJsonValue, IEquatable + { + public static readonly JsonNull Null = new JsonNull(); + + public JsonValueType Type + { + get { return JsonValueType.Null; } + } + + private JsonNull() + { + } + + public override bool Equals(object obj) + { + return Equals(obj as JsonNull); + } + + public bool Equals(IJsonValue other) + { + return Equals(other as JsonNull); + } + + public bool Equals(JsonNull other) + { + return other != null; + } + + public override int GetHashCode() + { + return 0; + } + + public string ToJsonString() + { + return ToString(); + } + + public override string ToString() + { + return "null"; + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Objects/JsonNumber.cs b/src/Squidex.Infrastructure/Json/Objects/JsonNumber.cs new file mode 100644 index 000000000..25c3c573f --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Objects/JsonNumber.cs @@ -0,0 +1,29 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Globalization; + +namespace Squidex.Infrastructure.Json.Objects +{ + public sealed class JsonNumber : JsonScalar + { + public override JsonValueType Type + { + get { return JsonValueType.Number; } + } + + internal JsonNumber(double value) + : base(value) + { + } + + public override string ToString() + { + return Value.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Objects/JsonObject.cs b/src/Squidex.Infrastructure/Json/Objects/JsonObject.cs new file mode 100644 index 000000000..c4974ce47 --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Objects/JsonObject.cs @@ -0,0 +1,135 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Squidex.Infrastructure.Json.Objects +{ + public class JsonObject : IReadOnlyDictionary, IJsonValue, IEquatable + { + private readonly Dictionary inner; + + public IJsonValue this[string key] + { + get + { + return inner[key]; + } + set + { + Guard.NotNullOrEmpty(key, nameof(key)); + + inner[key] = value ?? JsonValue.Null; + } + } + + public IEnumerable Keys + { + get { return inner.Keys; } + } + + public IEnumerable Values + { + get { return inner.Values; } + } + + public int Count + { + get { return inner.Count; } + } + + public JsonValueType Type + { + get { return JsonValueType.Array; } + } + + internal JsonObject() + { + inner = new Dictionary(); + } + + public JsonObject(JsonObject obj) + { + inner = new Dictionary(obj.inner); + } + + public JsonObject Add(string key, object value) + { + return Add(key, JsonValue.Create(value)); + } + + public JsonObject Add(string key, IJsonValue value) + { + inner[key] = value ?? JsonValue.Null; + + return this; + } + + public void Clear() + { + inner.Clear(); + } + + public bool Remove(string key) + { + return inner.Remove(key); + } + + public bool ContainsKey(string key) + { + return inner.ContainsKey(key); + } + + public bool TryGetValue(string key, out IJsonValue value) + { + return inner.TryGetValue(key, out value); + } + + public IEnumerator> GetEnumerator() + { + return inner.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return inner.GetEnumerator(); + } + + public override bool Equals(object obj) + { + return Equals(obj as JsonObject); + } + + public bool Equals(IJsonValue other) + { + return Equals(other as JsonObject); + } + + public bool Equals(JsonObject other) + { + return other != null && inner.EqualsDictionary(other.inner); + } + + public override int GetHashCode() + { + return inner.DictionaryHashCode(); + } + + public string ToJsonString() + { + return ToString(); + } + + public override string ToString() + { + return $"{{{string.Join(", ", this.Select(x => $"\"{x.Key}\":{x.Value.ToJsonString()}"))}}}"; + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Objects/JsonScalar.cs b/src/Squidex.Infrastructure/Json/Objects/JsonScalar.cs new file mode 100644 index 000000000..5ea9c2bd7 --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Objects/JsonScalar.cs @@ -0,0 +1,53 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; + +namespace Squidex.Infrastructure.Json.Objects +{ + public abstract class JsonScalar : IJsonValue, IEquatable> + { + public abstract JsonValueType Type { get; } + + public T Value { get; } + + protected JsonScalar(T value) + { + Value = value; + } + + public override bool Equals(object obj) + { + return Equals(obj as JsonScalar); + } + + public bool Equals(IJsonValue other) + { + return Equals(other as JsonScalar); + } + + public bool Equals(JsonScalar other) + { + return other != null && other.Type == Type && Equals(other.Value, Value); + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } + + public override string ToString() + { + return Value.ToString(); + } + + public virtual string ToJsonString() + { + return ToString(); + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Objects/JsonString.cs b/src/Squidex.Infrastructure/Json/Objects/JsonString.cs new file mode 100644 index 000000000..4aeccc93f --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Objects/JsonString.cs @@ -0,0 +1,27 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Json.Objects +{ + public sealed class JsonString : JsonScalar + { + public override JsonValueType Type + { + get { return JsonValueType.String; } + } + + internal JsonString(string value) + : base(value) + { + } + + public override string ToJsonString() + { + return $"\"{Value}\""; + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Objects/JsonValue.cs b/src/Squidex.Infrastructure/Json/Objects/JsonValue.cs new file mode 100644 index 000000000..922039f5e --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Objects/JsonValue.cs @@ -0,0 +1,136 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using NodaTime; + +#pragma warning disable RECS0018 // Comparison of floating point numbers with equality operator + +namespace Squidex.Infrastructure.Json.Objects +{ + public static class JsonValue + { + public static readonly IJsonValue Empty = new JsonString(string.Empty); + + public static readonly IJsonValue True = JsonBoolean.True; + public static readonly IJsonValue False = JsonBoolean.False; + + public static readonly IJsonValue Null = JsonNull.Null; + + public static readonly IJsonValue Zero = new JsonNumber(0); + + public static JsonArray Array() + { + return new JsonArray(); + } + + public static JsonArray Array(params object[] values) + { + return new JsonArray(values); + } + + public static JsonObject Object() + { + return new JsonObject(); + } + + public static IJsonValue Create(object value) + { + if (value == null) + { + return Null; + } + + if (value is IJsonValue v) + { + return v; + } + + switch (value) + { + case string s: + return Create(s); + case bool b: + return Create(b); + case float f: + return Create(f); + case double d: + return Create(d); + case int i: + return Create(i); + case long l: + return Create(l); + case Instant i: + return Create(i); + } + + throw new ArgumentException("Invalid json type"); + } + + public static IJsonValue Create(bool value) + { + return value ? True : False; + } + + public static IJsonValue Create(double value) + { + Guard.ValidNumber(value, nameof(value)); + + if (value == 0) + { + return Zero; + } + + return new JsonNumber(value); + } + + public static IJsonValue Create(Instant? value) + { + if (value == null) + { + return Null; + } + + return Create(value.Value.ToString()); + } + + public static IJsonValue Create(double? value) + { + if (value == null) + { + return Null; + } + + return Create(value.Value); + } + + public static IJsonValue Create(bool? value) + { + if (value == null) + { + return Null; + } + + return Create(value.Value); + } + + public static IJsonValue Create(string value) + { + if (value == null) + { + return Null; + } + + if (value.Length == 0) + { + return Empty; + } + + return new JsonString(value); + } + } +} diff --git a/src/Squidex.Infrastructure/Json/Objects/JsonValueType.cs b/src/Squidex.Infrastructure/Json/Objects/JsonValueType.cs new file mode 100644 index 000000000..8b4a13022 --- /dev/null +++ b/src/Squidex.Infrastructure/Json/Objects/JsonValueType.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Json.Objects +{ + public enum JsonValueType + { + Array, + Boolean, + Null, + Number, + Object, + String + } +} diff --git a/src/Squidex.Infrastructure/Json/PropertiesBagConverter.cs b/src/Squidex.Infrastructure/Json/PropertiesBagConverter.cs deleted file mode 100644 index f42642fb0..000000000 --- a/src/Squidex.Infrastructure/Json/PropertiesBagConverter.cs +++ /dev/null @@ -1,78 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using Newtonsoft.Json; -using NodaTime; -using NodaTime.Extensions; - -namespace Squidex.Infrastructure.Json -{ - public sealed class PropertiesBagConverter : JsonClassConverter where T : PropertiesBag, new() - { - protected override void WriteValue(JsonWriter writer, T value, JsonSerializer serializer) - { - writer.WriteStartObject(); - - foreach (var kvp in value.Properties) - { - writer.WritePropertyName(kvp.Key); - - if (kvp.Value.RawValue is Instant) - { - writer.WriteValue(kvp.Value.ToString()); - } - else - { - writer.WriteValue(kvp.Value.RawValue); - } - } - - writer.WriteEndObject(); - } - - protected override T ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer) - { - if (reader.TokenType != JsonToken.StartObject) - { - throw new JsonException($"Expected Object, but got {reader.TokenType}."); - } - - var properties = new T(); - - while (reader.Read()) - { - if (reader.TokenType != JsonToken.PropertyName) - { - break; - } - - var key = reader.Value.ToString(); - - reader.Read(); - - var value = reader.Value; - - if (value is DateTime dateTime) - { - properties.Set(key, dateTime.ToInstant()); - } - else - { - properties.Set(key, value); - } - } - - return properties; - } - - public override bool CanConvert(Type objectType) - { - return objectType == typeof(T); - } - } -} diff --git a/src/Squidex.Infrastructure/Language.cs b/src/Squidex.Infrastructure/Language.cs index c3c6afb71..981d7ee47 100644 --- a/src/Squidex.Infrastructure/Language.cs +++ b/src/Squidex.Infrastructure/Language.cs @@ -18,7 +18,7 @@ namespace Squidex.Infrastructure private static Language AddLanguage(string iso2Code, string englishName) { - return AllLanguagesField.GetOrAdd(iso2Code, code => new Language(code, englishName)); + return AllLanguagesField.GetOrAdd(iso2Code, englishName, (c, n) => new Language(c, n)); } public static Language GetLanguage(string iso2Code) @@ -35,7 +35,7 @@ namespace Squidex.Infrastructure } } - public static IEnumerable AllLanguages + public static IReadOnlyCollection AllLanguages { get { return AllLanguagesField.Values; } } diff --git a/src/Squidex.Infrastructure/Log/IArrayWriter.cs b/src/Squidex.Infrastructure/Log/IArrayWriter.cs index 7d5c96a48..8ec8b803a 100644 --- a/src/Squidex.Infrastructure/Log/IArrayWriter.cs +++ b/src/Squidex.Infrastructure/Log/IArrayWriter.cs @@ -12,12 +12,17 @@ namespace Squidex.Infrastructure.Log public interface IArrayWriter { IArrayWriter WriteValue(string value); + IArrayWriter WriteValue(double value); + IArrayWriter WriteValue(long value); + IArrayWriter WriteValue(bool value); IArrayWriter WriteValue(TimeSpan value); + IArrayWriter WriteValue(DateTime value); + IArrayWriter WriteValue(DateTimeOffset value); IArrayWriter WriteObject(Action objectWriter); diff --git a/src/Squidex.Infrastructure/Log/IObjectWriter.cs b/src/Squidex.Infrastructure/Log/IObjectWriter.cs index 1c8ce1f95..fc133875e 100644 --- a/src/Squidex.Infrastructure/Log/IObjectWriter.cs +++ b/src/Squidex.Infrastructure/Log/IObjectWriter.cs @@ -12,15 +12,21 @@ namespace Squidex.Infrastructure.Log public interface IObjectWriter { IObjectWriter WriteProperty(string property, string value); + IObjectWriter WriteProperty(string property, double value); + IObjectWriter WriteProperty(string property, long value); + IObjectWriter WriteProperty(string property, bool value); IObjectWriter WriteProperty(string property, TimeSpan value); + IObjectWriter WriteProperty(string property, DateTime value); + IObjectWriter WriteProperty(string property, DateTimeOffset value); IObjectWriter WriteObject(string property, Action objectWriter); + IObjectWriter WriteArray(string property, Action arrayWriter); } } diff --git a/src/Squidex.Infrastructure/Log/IObjectWriterFactory.cs b/src/Squidex.Infrastructure/Log/IObjectWriterFactory.cs new file mode 100644 index 000000000..65b992ee0 --- /dev/null +++ b/src/Squidex.Infrastructure/Log/IObjectWriterFactory.cs @@ -0,0 +1,16 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Infrastructure.Log +{ + public interface IObjectWriterFactory + { + IObjectWriter Create(); + + void Release(IObjectWriter writer); + } +} diff --git a/src/Squidex.Infrastructure/Log/JsonLogWriter.cs b/src/Squidex.Infrastructure/Log/JsonLogWriter.cs index 7d9bdf21f..b90cf64eb 100644 --- a/src/Squidex.Infrastructure/Log/JsonLogWriter.cs +++ b/src/Squidex.Infrastructure/Log/JsonLogWriter.cs @@ -14,18 +14,37 @@ namespace Squidex.Infrastructure.Log { public sealed class JsonLogWriter : IObjectWriter, IArrayWriter { - private readonly bool extraLine; + private readonly Formatting formatting; + private readonly bool formatLine; private readonly StringWriter textWriter = new StringWriter(); - private readonly JsonWriter jsonWriter; + private JsonWriter jsonWriter; - public JsonLogWriter(Formatting formatting = Formatting.None, bool extraLine = false) + public int BufferSize { - this.extraLine = extraLine; + get { return textWriter.GetStringBuilder().Capacity; } + } + + internal JsonLogWriter(Formatting formatting, bool formatLine) + { + this.formatLine = formatLine; + this.formatting = formatting; + + Start(); + } + private void Start() + { jsonWriter = new JsonTextWriter(textWriter) { Formatting = formatting }; jsonWriter.WriteStartObject(); } + internal void Reset() + { + textWriter.GetStringBuilder().Clear(); + + Start(); + } + IArrayWriter IArrayWriter.WriteValue(string value) { jsonWriter.WriteValue(value); @@ -77,7 +96,7 @@ namespace Squidex.Infrastructure.Log IObjectWriter IObjectWriter.WriteProperty(string property, string value) { - jsonWriter.WritePropertyName(property.ToCamelCase()); + jsonWriter.WritePropertyName(Format(property)); jsonWriter.WriteValue(value); return this; @@ -85,7 +104,7 @@ namespace Squidex.Infrastructure.Log IObjectWriter IObjectWriter.WriteProperty(string property, double value) { - jsonWriter.WritePropertyName(property.ToCamelCase()); + jsonWriter.WritePropertyName(Format(property)); jsonWriter.WriteValue(value); return this; @@ -93,7 +112,7 @@ namespace Squidex.Infrastructure.Log IObjectWriter IObjectWriter.WriteProperty(string property, long value) { - jsonWriter.WritePropertyName(property.ToCamelCase()); + jsonWriter.WritePropertyName(Format(property)); jsonWriter.WriteValue(value); return this; @@ -101,7 +120,7 @@ namespace Squidex.Infrastructure.Log IObjectWriter IObjectWriter.WriteProperty(string property, bool value) { - jsonWriter.WritePropertyName(property.ToCamelCase()); + jsonWriter.WritePropertyName(Format(property)); jsonWriter.WriteValue(value); return this; @@ -109,7 +128,7 @@ namespace Squidex.Infrastructure.Log IObjectWriter IObjectWriter.WriteProperty(string property, DateTime value) { - jsonWriter.WritePropertyName(property.ToCamelCase()); + jsonWriter.WritePropertyName(Format(property)); jsonWriter.WriteValue(value.ToString("o", CultureInfo.InvariantCulture)); return this; @@ -117,7 +136,7 @@ namespace Squidex.Infrastructure.Log IObjectWriter IObjectWriter.WriteProperty(string property, DateTimeOffset value) { - jsonWriter.WritePropertyName(property.ToCamelCase()); + jsonWriter.WritePropertyName(Format(property)); jsonWriter.WriteValue(value.ToString("o", CultureInfo.InvariantCulture)); return this; @@ -125,7 +144,7 @@ namespace Squidex.Infrastructure.Log IObjectWriter IObjectWriter.WriteProperty(string property, TimeSpan value) { - jsonWriter.WritePropertyName(property.ToCamelCase()); + jsonWriter.WritePropertyName(Format(property)); jsonWriter.WriteValue(value); return this; @@ -133,7 +152,7 @@ namespace Squidex.Infrastructure.Log IObjectWriter IObjectWriter.WriteObject(string property, Action objectWriter) { - jsonWriter.WritePropertyName(property); + jsonWriter.WritePropertyName(Format(property)); jsonWriter.WriteStartObject(); objectWriter?.Invoke(this); @@ -145,7 +164,7 @@ namespace Squidex.Infrastructure.Log IObjectWriter IObjectWriter.WriteArray(string property, Action arrayWriter) { - jsonWriter.WritePropertyName(property.ToCamelCase()); + jsonWriter.WritePropertyName(Format(property)); jsonWriter.WriteStartArray(); arrayWriter?.Invoke(this); @@ -166,18 +185,26 @@ namespace Squidex.Infrastructure.Log return this; } + private static string Format(string property) + { + if (ReferenceEquals(string.IsInterned(property), property)) + { + return property; + } + + return property.ToCamelCase(); + } + public override string ToString() { jsonWriter.WriteEndObject(); - var result = textWriter.ToString(); - - if (extraLine) + if (formatLine) { - result += Environment.NewLine; + textWriter.WriteLine(); } - return result; + return textWriter.ToString(); } } } diff --git a/src/Squidex.Infrastructure/Log/JsonLogWriterFactory.cs b/src/Squidex.Infrastructure/Log/JsonLogWriterFactory.cs new file mode 100644 index 000000000..7ff85b6ea --- /dev/null +++ b/src/Squidex.Infrastructure/Log/JsonLogWriterFactory.cs @@ -0,0 +1,61 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Concurrent; +using Newtonsoft.Json; + +namespace Squidex.Infrastructure.Log +{ + public sealed class JsonLogWriterFactory : IObjectWriterFactory + { + private const int MaxPoolSize = 10; + private const int MaxCapacity = 5000; + private readonly ConcurrentStack pool = new ConcurrentStack(); + private readonly Formatting formatting; + private readonly bool formatLine; + + public JsonLogWriterFactory(Formatting formatting = Formatting.None, bool formatLine = false) + { + this.formatting = formatting; + this.formatLine = formatLine; + } + + public static JsonLogWriterFactory Default() + { + return new JsonLogWriterFactory(); + } + + public static JsonLogWriterFactory Readable() + { + return new JsonLogWriterFactory(Formatting.Indented, true); + } + + public IObjectWriter Create() + { + if (pool.TryPop(out var writer)) + { + writer.Reset(); + } + else + { + writer = new JsonLogWriter(formatting, formatLine); + } + + return writer; + } + + public void Release(IObjectWriter writer) + { + var jsonWriter = (JsonLogWriter)writer; + + if (pool.Count < MaxPoolSize && jsonWriter.BufferSize < MaxCapacity) + { + pool.Push(jsonWriter); + } + } + } +} diff --git a/src/Squidex.Infrastructure/Log/SemanticLog.cs b/src/Squidex.Infrastructure/Log/SemanticLog.cs index 7816db42f..09293315d 100644 --- a/src/Squidex.Infrastructure/Log/SemanticLog.cs +++ b/src/Squidex.Infrastructure/Log/SemanticLog.cs @@ -13,20 +13,21 @@ namespace Squidex.Infrastructure.Log { public sealed class SemanticLog : ISemanticLog { - private readonly IEnumerable channels; - private readonly IEnumerable appenders; - private readonly Func writerFactory; + private readonly ILogChannel[] channels; + private readonly ILogAppender[] appenders; + private readonly IObjectWriterFactory writerFactory; public SemanticLog( IEnumerable channels, IEnumerable appenders, - Func writerFactory) + IObjectWriterFactory writerFactory) { Guard.NotNull(channels, nameof(channels)); Guard.NotNull(appenders, nameof(appenders)); + Guard.NotNull(writerFactory, nameof(writerFactory)); - this.channels = channels; - this.appenders = appenders; + this.channels = channels.ToArray(); + this.appenders = appenders.ToArray(); this.writerFactory = writerFactory; } @@ -38,11 +39,11 @@ namespace Squidex.Infrastructure.Log List exceptions = null; - foreach (var channel in channels) + for (var i = 0; i < channels.Length; i++) { try { - channel.Log(logLevel, formattedText); + channels[i].Log(logLevel, formattedText); } catch (Exception ex) { @@ -63,18 +64,25 @@ namespace Squidex.Infrastructure.Log private string FormatText(SemanticLogLevel logLevel, Action objectWriter) { - var writer = writerFactory(); + var writer = writerFactory.Create(); - writer.WriteProperty(nameof(logLevel), logLevel.ToString()); + try + { + writer.WriteProperty(nameof(logLevel), logLevel.ToString()); + + objectWriter(writer); - objectWriter(writer); + for (var i = 0; i < appenders.Length; i++) + { + appenders[i].Append(writer); + } - foreach (var appender in appenders) + return writer.ToString(); + } + finally { - appender.Append(writer); + writerFactory.Release(writer); } - - return writer.ToString(); } public ISemanticLog CreateScope(Action objectWriter) diff --git a/src/Squidex.Infrastructure/NamedId{T}.cs b/src/Squidex.Infrastructure/NamedId{T}.cs index e8f99f6d4..eeeab8ab9 100644 --- a/src/Squidex.Infrastructure/NamedId{T}.cs +++ b/src/Squidex.Infrastructure/NamedId{T}.cs @@ -6,7 +6,8 @@ // ========================================================================== using System; -using System.Linq; + +#pragma warning disable RECS0108 // Warns about static fields in generic types namespace Squidex.Infrastructure { @@ -14,6 +15,8 @@ namespace Squidex.Infrastructure public sealed class NamedId : IEquatable> { + private static readonly int GuidLength = Guid.Empty.ToString().Length; + public T Id { get; } public string Name { get; } @@ -48,23 +51,51 @@ namespace Squidex.Infrastructure return (Id.GetHashCode() * 397) ^ Name.GetHashCode(); } - public static NamedId Parse(string value, Parser parser) + public static bool TryParse(string value, Parser parser, out NamedId result) { - Guard.NotNull(value, nameof(value)); - - var parts = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length < 2) + if (value != null) { - throw new ArgumentException("Named id must have more than 2 parts divided by commata."); + if (typeof(T) == typeof(Guid)) + { + if (value.Length > GuidLength + 1 && value[GuidLength] == ',') + { + if (parser(value.Substring(0, GuidLength), out var id)) + { + result = new NamedId(id, value.Substring(GuidLength + 1)); + + return true; + } + } + } + else + { + var index = value.IndexOf(','); + + if (index > 0 && index < value.Length - 1) + { + if (parser(value.Substring(0, index), out var id)) + { + result = new NamedId(id, value.Substring(index + 1)); + + return true; + } + } + } } - if (!parser(parts[0], out var id)) + result = null; + + return false; + } + + public static NamedId Parse(string value, Parser parser) + { + if (!TryParse(value, parser, out var result)) { - throw new ArgumentException("Named id must be a valid guid."); + throw new ArgumentException("Named id must have at least 2 parts divided by commata.", nameof(value)); } - return new NamedId(id, string.Join(",", parts.Skip(1))); + return result; } } } diff --git a/src/Squidex.Infrastructure/Orleans/J.cs b/src/Squidex.Infrastructure/Orleans/J.cs index 9f89f09d6..aee59a82d 100644 --- a/src/Squidex.Infrastructure/Orleans/J.cs +++ b/src/Squidex.Infrastructure/Orleans/J.cs @@ -6,13 +6,15 @@ // ========================================================================== using System.Threading.Tasks; -using Newtonsoft.Json; +using Squidex.Infrastructure.Json; + +#pragma warning disable SA1401 // Fields must be private namespace Squidex.Infrastructure.Orleans { public static class J { - internal static readonly JsonSerializer DefaultSerializer = JsonSerializer.CreateDefault(); + public static IJsonSerializer DefaultSerializer; public static J AsJ(this T value) { diff --git a/src/Squidex.Infrastructure/Orleans/J{T}.cs b/src/Squidex.Infrastructure/Orleans/J{T}.cs index e39b12664..8946ef845 100644 --- a/src/Squidex.Infrastructure/Orleans/J{T}.cs +++ b/src/Squidex.Infrastructure/Orleans/J{T}.cs @@ -6,13 +6,12 @@ // ========================================================================== using System; -using System.IO; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; using Orleans.CodeGeneration; using Orleans.Concurrency; using Orleans.Serialization; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Log; namespace Squidex.Infrastructure.Orleans @@ -22,7 +21,6 @@ namespace Squidex.Infrastructure.Orleans { public T Value { get; } - [JsonConstructor] public J(T value) { Value = value; @@ -63,12 +61,7 @@ namespace Squidex.Infrastructure.Orleans var stream = new StreamWriterWrapper(context.StreamWriter); - using (var writer = new JsonTextWriter(new StreamWriter(stream))) - { - jsonSerializer.Serialize(writer, input); - - writer.Flush(); - } + jsonSerializer.Serialize(input, stream); } } @@ -81,18 +74,15 @@ namespace Squidex.Infrastructure.Orleans var stream = new StreamReaderWrapper(context.StreamReader); - using (var reader = new JsonTextReader(new StreamReader(stream))) - { - return jsonSerializer.Deserialize(reader, expected); - } + return jsonSerializer.Deserialize(stream, expected); } } - private static JsonSerializer GetSerializer(ISerializerContext context) + private static IJsonSerializer GetSerializer(ISerializerContext context) { try { - return context?.ServiceProvider?.GetService() ?? J.DefaultSerializer; + return context?.ServiceProvider?.GetRequiredService() ?? J.DefaultSerializer; } catch { diff --git a/src/Squidex.Infrastructure/PropertiesBag.cs b/src/Squidex.Infrastructure/PropertiesBag.cs deleted file mode 100644 index 511c76368..000000000 --- a/src/Squidex.Infrastructure/PropertiesBag.cs +++ /dev/null @@ -1,112 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Dynamic; - -namespace Squidex.Infrastructure -{ - public class PropertiesBag : DynamicObject - { - private static readonly PropertyValue FallbackValue = new PropertyValue(null); - private readonly Dictionary internalDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - - public int Count - { - get { return internalDictionary.Count; } - } - - public IReadOnlyDictionary Properties - { - get { return internalDictionary; } - } - - public IEnumerable PropertyNames - { - get { return internalDictionary.Keys; } - } - - public PropertyValue this[string propertyName] - { - get - { - Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); - - return internalDictionary.GetOrDefault(propertyName) ?? FallbackValue; - } - } - - public override IEnumerable GetDynamicMemberNames() - { - return internalDictionary.Keys; - } - - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - result = this[binder.Name]; - - return true; - } - - public override bool TrySetMember(SetMemberBinder binder, object value) - { - internalDictionary[binder.Name] = new PropertyValue(value); - - return true; - } - - public bool Contains(string propertyName) - { - Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); - - return internalDictionary.ContainsKey(propertyName); - } - - public bool Remove(string propertyName) - { - Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); - - return internalDictionary.Remove(propertyName); - } - - public PropertiesBag Set(string propertyName, object value) - { - Guard.NotNullOrEmpty(propertyName, nameof(propertyName)); - - internalDictionary[propertyName] = new PropertyValue(value); - - return this; - } - - public bool Rename(string oldPropertyName, string newPropertyName) - { - Guard.NotNullOrEmpty(oldPropertyName, nameof(oldPropertyName)); - Guard.NotNullOrEmpty(newPropertyName, nameof(newPropertyName)); - - if (internalDictionary.ContainsKey(newPropertyName)) - { - throw new ArgumentException($"An property with the key '{newPropertyName}' already exists.", newPropertyName); - } - - if (string.Equals(oldPropertyName, newPropertyName, StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException($"The property names '{newPropertyName}' are equal.", newPropertyName); - } - - if (!internalDictionary.TryGetValue(oldPropertyName, out var property)) - { - return false; - } - - internalDictionary[newPropertyName] = property; - internalDictionary.Remove(oldPropertyName); - - return true; - } - } -} diff --git a/src/Squidex.Infrastructure/PropertyValue.cs b/src/Squidex.Infrastructure/PropertyValue.cs deleted file mode 100644 index 8187e0b7a..000000000 --- a/src/Squidex.Infrastructure/PropertyValue.cs +++ /dev/null @@ -1,235 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Dynamic; -using System.Globalization; -using NodaTime; -using NodaTime.Text; - -namespace Squidex.Infrastructure -{ - public sealed class PropertyValue : DynamicObject - { - private readonly object rawValue; - - private static readonly Dictionary> Parsers = - new Dictionary> - { - { typeof(string), (p, c) => p.ToString() }, - { typeof(bool), (p, c) => p.ToBoolean(c) }, - { typeof(bool?), (p, c) => p.ToNullableBoolean(c) }, - { typeof(float), (p, c) => p.ToSingle(c) }, - { typeof(float?), (p, c) => p.ToNullableSingle(c) }, - { typeof(double), (p, c) => p.ToDouble(c) }, - { typeof(double?), (p, c) => p.ToNullableDouble(c) }, - { typeof(int), (p, c) => p.ToInt32(c) }, - { typeof(int?), (p, c) => p.ToNullableInt32(c) }, - { typeof(long), (p, c) => p.ToInt64(c) }, - { typeof(long?), (p, c) => p.ToNullableInt64(c) }, - { typeof(Instant), (p, c) => p.ToInstant(c) }, - { typeof(Instant?), (p, c) => p.ToNullableInstant(c) }, - { typeof(Guid), (p, c) => p.ToGuid(c) }, - { typeof(Guid?), (p, c) => p.ToNullableGuid(c) } - }; - - public object RawValue - { - get { return rawValue; } - } - - internal PropertyValue(object rawValue) - { - if (rawValue != null && !Parsers.ContainsKey(rawValue.GetType())) - { - throw new InvalidOperationException($"The type '{rawValue.GetType()}' is not supported."); - } - - this.rawValue = rawValue; - } - - public override bool TryConvert(ConvertBinder binder, out object result) - { - result = null; - - if (!Parsers.TryGetValue(binder.Type, out var parser)) - { - return false; - } - - result = parser(this, CultureInfo.InvariantCulture); - - return true; - } - - public override string ToString() - { - return rawValue?.ToString(); - } - - public bool ToBoolean(CultureInfo culture) - { - return ToOrParseValue(culture, ParseBoolean); - } - - public bool? ToNullableBoolean(CultureInfo culture) - { - return ToNullableOrParseValue(culture, ParseBoolean); - } - - public float ToSingle(CultureInfo culture) - { - return ToOrParseValue(culture, x => float.Parse(x, culture)); - } - - public float? ToNullableSingle(CultureInfo culture) - { - return ToNullableOrParseValue(culture, x => float.Parse(x, culture)); - } - - public double ToDouble(CultureInfo culture) - { - return ToOrParseValue(culture, x => double.Parse(x, culture)); - } - - public double? ToNullableDouble(CultureInfo culture) - { - return ToNullableOrParseValue(culture, x => double.Parse(x, culture)); - } - - public int ToInt32(CultureInfo culture) - { - return ToOrParseValue(culture, x => int.Parse(x, culture)); - } - - public int? ToNullableInt32(CultureInfo culture) - { - return ToNullableOrParseValue(culture, x => int.Parse(x, culture)); - } - - public long ToInt64(CultureInfo culture) - { - return ToOrParseValue(culture, x => long.Parse(x, culture)); - } - - public long? ToNullableInt64(CultureInfo culture) - { - return ToNullableOrParseValue(culture, x => long.Parse(x, culture)); - } - - public Instant ToInstant(CultureInfo culture) - { - return ToOrParseValue(culture, x => InstantPattern.General.Parse(x).Value); - } - - public Instant? ToNullableInstant(CultureInfo culture) - { - return ToNullableOrParseValue(culture, x => InstantPattern.General.Parse(x).Value); - } - - public Guid ToGuid(CultureInfo culture) - { - return ToOrParseValue(culture, Guid.Parse); - } - - public Guid? ToNullableGuid(CultureInfo culture) - { - return ToNullableOrParseValue(culture, Guid.Parse); - } - - private T? ToNullableOrParseValue(IFormatProvider culture, Func parser) where T : struct - { - return TryParse(culture, parser, out var result) ? result : (T?)null; - } - - private T ToOrParseValue(IFormatProvider culture, Func parser) - { - return TryParse(culture, parser, out var result) ? result : default(T); - } - - private bool TryParse(IFormatProvider culture, Func parser, out T result) - { - var value = rawValue; - - if (value != null) - { - var valueType = value.GetType(); - - if (valueType == typeof(T)) - { - result = (T)value; - } - else if (valueType == typeof(string)) - { - result = Parse(parser, valueType, value); - } - else - { - result = Convert(culture, value, valueType); - } - - return true; - } - - result = default(T); - - return false; - } - - private static T Convert(IFormatProvider culture, object value, Type valueType) - { - var requestedType = typeof(T); - - try - { - return (T)System.Convert.ChangeType(value, requestedType, culture); - } - catch (OverflowException) - { - var message = $"The property has type '{valueType}' and cannot be casted to '{requestedType}' because it is either too small or large."; - - throw new InvalidCastException(message); - } - catch (InvalidCastException) - { - var message = $"The property has type '{valueType}' and cannot be casted to '{requestedType}'."; - - throw new InvalidCastException(message); - } - } - - private static T Parse(Func parser, Type valueType, object value) - { - var requestedType = typeof(T); - - try - { - return parser(value.ToString()); - } - catch (Exception ex) - { - var message = $"The property has type '{valueType}' and cannot be casted to '{requestedType}'."; - - throw new InvalidCastException(message, ex); - } - } - - private static bool ParseBoolean(string value) - { - switch (value) - { - case "1": - return true; - case "0": - return false; - default: - return bool.Parse(value); - } - } - } -} diff --git a/src/Squidex.Infrastructure/Queries/FilterValue.cs b/src/Squidex.Infrastructure/Queries/FilterValue.cs index d340a4c88..7c07827f8 100644 --- a/src/Squidex.Infrastructure/Queries/FilterValue.cs +++ b/src/Squidex.Infrastructure/Queries/FilterValue.cs @@ -8,6 +8,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.Linq; using NodaTime; @@ -126,26 +127,23 @@ namespace Squidex.Infrastructure.Queries { return $"[{string.Join(", ", list.OfType().Select(ToString).ToArray())}]"; } - else - { - return ToString(Value); - } + + return ToString(Value); } private string ToString(object value) { - if (ValueType == FilterValueType.String) - { - return $"'{value.ToString().Replace("'", "\\'")}'"; - } - else if (value == null) + if (value == null) { return "null"; } - else + + if (value is string s) { - return value.ToString(); + return $"'{s.Replace("'", "\\'")}'"; } + + return string.Format(CultureInfo.InvariantCulture, "{0}", value); } } } diff --git a/src/Squidex.Infrastructure/RefToken.cs b/src/Squidex.Infrastructure/RefToken.cs index d9298b6aa..5d0c1d215 100644 --- a/src/Squidex.Infrastructure/RefToken.cs +++ b/src/Squidex.Infrastructure/RefToken.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Linq; namespace Squidex.Infrastructure { @@ -26,20 +25,6 @@ namespace Squidex.Infrastructure Identifier = identifier; } - public static RefToken Parse(string input) - { - Guard.NotNullOrEmpty(input, nameof(input)); - - var parts = input.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length < 2) - { - throw new ArgumentException("Input must have more than 2 parts divided by colon.", nameof(input)); - } - - return new RefToken(parts[0], string.Join(":", parts.Skip(1))); - } - public override string ToString() { return $"{Type}:{Identifier}"; @@ -59,5 +44,34 @@ namespace Squidex.Infrastructure { return (Type.GetHashCode() * 397) ^ Identifier.GetHashCode(); } + + public static bool TryParse(string value, out RefToken result) + { + if (value != null) + { + var idx = value.IndexOf(':'); + + if (idx > 0 && idx < value.Length - 1) + { + result = new RefToken(value.Substring(0, idx), value.Substring(idx + 1)); + + return true; + } + } + + result = null; + + return false; + } + + public static RefToken Parse(string value) + { + if (!TryParse(value, out var result)) + { + throw new ArgumentException("Ref token must have more than 2 parts divided by colon.", nameof(value)); + } + + return result; + } } } diff --git a/src/Squidex.Infrastructure/Security/PermissionSet.cs b/src/Squidex.Infrastructure/Security/PermissionSet.cs index a7b138927..08f702b19 100644 --- a/src/Squidex.Infrastructure/Security/PermissionSet.cs +++ b/src/Squidex.Infrastructure/Security/PermissionSet.cs @@ -14,7 +14,7 @@ namespace Squidex.Infrastructure.Security { public sealed class PermissionSet : IReadOnlyCollection { - public static readonly PermissionSet Empty = new PermissionSet(new string[0]); + public static readonly PermissionSet Empty = new PermissionSet(Array.Empty()); private readonly List permissions; private readonly Lazy display; diff --git a/src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs b/src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs index 582011a20..e7b30bca6 100644 --- a/src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs +++ b/src/Squidex.Infrastructure/States/DefaultStreamNameResolver.cs @@ -1,4 +1,4 @@ -// ========================================================================== + // ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschränkt) diff --git a/src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs b/src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs index d07e2d7a4..3aa0e09a3 100644 --- a/src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs +++ b/src/Squidex.Infrastructure/States/Persistence{TSnapshot,TKey}.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; #pragma warning disable RECS0012 // 'if' statement can be re-written as 'switch' statement @@ -190,7 +191,7 @@ namespace Squidex.Infrastructure.States private EventData[] GetEventData(Envelope[] events, Guid commitId) { - return events.Select(x => eventDataFormatter.ToEventData(x, commitId, true)).ToArray(); + return events.ToArray(x => eventDataFormatter.ToEventData(x, commitId, true)); } private string GetStreamName() diff --git a/src/Squidex.Infrastructure/StringExtensions.cs b/src/Squidex.Infrastructure/StringExtensions.cs index 55349977a..bfa46b900 100644 --- a/src/Squidex.Infrastructure/StringExtensions.cs +++ b/src/Squidex.Infrastructure/StringExtensions.cs @@ -15,6 +15,7 @@ namespace Squidex.Infrastructure { public static class StringExtensions { + private const char NullChar = (char)0; private static readonly Regex SlugRegex = new Regex("^[a-z0-9]+(\\-[a-z0-9]+)*$", RegexOptions.Compiled); private static readonly Regex PropertyNameRegex = new Regex("^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*$", RegexOptions.Compiled); private static readonly Dictionary LowerCaseDiacritics; @@ -325,36 +326,97 @@ namespace Squidex.Infrastructure public static string ToPascalCase(this string value) { - var sb = new StringBuilder(); + if (string.IsNullOrWhiteSpace(value)) + { + return string.Empty; + } + + var sb = new StringBuilder(value.Length); - foreach (var part in value.Split(new[] { '-', '_', ' ' }, StringSplitOptions.RemoveEmptyEntries)) + var last = NullChar; + var length = 0; + + for (var i = 0; i < value.Length; i++) { - if (part.Length < 2) + var c = value[i]; + + if (c == '-' || c == '_' || c == ' ') { - sb.Append(part.ToUpper()); + if (last != NullChar) + { + sb.Append(char.ToUpperInvariant(last)); + } + + last = NullChar; + length = 0; } else { - sb.Append(char.ToUpper(part[0])); - sb.Append(part.Substring(1)); + if (length > 1) + { + sb.Append(c); + } + else if (length == 0) + { + last = c; + } + else + { + sb.Append(char.ToUpperInvariant(last)); + sb.Append(c); + + last = NullChar; + } + + length++; } } + if (last != NullChar) + { + sb.Append(char.ToUpperInvariant(last)); + } + return sb.ToString(); } public static string ToKebabCase(this string value) { - var sb = new StringBuilder(); + if (value.Length == 0) + { + return string.Empty; + } + + var sb = new StringBuilder(value.Length); - foreach (var part in value.Split(new[] { '-', '_', ' ' }, StringSplitOptions.RemoveEmptyEntries)) + var length = 0; + + for (var i = 0; i < value.Length; i++) { - if (sb.Length > 0) + var c = value[i]; + + if (c == '-' || c == '_' || c == ' ') { - sb.Append("-"); + length = 0; } + else + { + if (length > 0) + { + sb.Append(char.ToLowerInvariant(c)); + } + else + { + if (sb.Length > 0) + { + sb.Append('-'); + } - sb.Append(part.ToLower()); + sb.Append(char.ToLowerInvariant(c)); + } + + length++; + } } return sb.ToString(); @@ -362,16 +424,80 @@ namespace Squidex.Infrastructure public static string ToCamelCase(this string value) { - value = value.ToPascalCase(); + if (value.Length == 0) + { + return string.Empty; + } - if (value.Length < 2) + var sb = new StringBuilder(value.Length); + + var last = NullChar; + var length = 0; + + for (var i = 0; i < value.Length; i++) { - return value.ToLower(); + var c = value[i]; + + if (c == '-' || c == '_' || c == ' ') + { + if (last != NullChar) + { + if (sb.Length > 0) + { + sb.Append(char.ToUpperInvariant(last)); + } + else + { + sb.Append(char.ToLowerInvariant(last)); + } + } + + last = NullChar; + length = 0; + } + else + { + if (length > 1) + { + sb.Append(c); + } + else if (length == 0) + { + last = c; + } + else + { + if (sb.Length > 0) + { + sb.Append(char.ToUpperInvariant(last)); + } + else + { + sb.Append(char.ToLowerInvariant(last)); + } + + sb.Append(c); + + last = NullChar; + } + + length++; + } } - else + + if (last != NullChar) { - return char.ToLower(value[0]) + value.Substring(1); + if (sb.Length > 0) + { + sb.Append(char.ToUpperInvariant(last)); + } + else + { + sb.Append(char.ToLowerInvariant(last)); + } } + + return sb.ToString(); } public static string Slugify(this string value, ISet preserveHash = null, bool singleCharDiactric = false, char separator = '-') diff --git a/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs b/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs index 3dcc82b19..2cb0240f7 100644 --- a/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs +++ b/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs @@ -98,13 +98,13 @@ namespace Squidex.Infrastructure.UsageTracking public Task TrackAsync(string key, string category, double weight, double elapsedMs) { - Guard.NotNull(key, nameof(key)); + key = GetKey(key); ThrowIfDisposed(); if (weight > 0) { - category = CleanCategory(category); + category = GetCategory(category); usages.AddOrUpdate((key, category), _ => new Usage(elapsedMs, weight), (k, x) => x.Add(elapsedMs, weight)); } @@ -114,12 +114,12 @@ namespace Squidex.Infrastructure.UsageTracking public async Task>> QueryAsync(string key, DateTime fromDate, DateTime toDate) { - Guard.NotNull(key, nameof(key)); + key = GetKey(key); ThrowIfDisposed(); var usagesFlat = await usageRepository.QueryAsync(key, fromDate, toDate); - var usagesByCategory = usagesFlat.GroupBy(x => CleanCategory(x.Category)).ToDictionary(x => x.Key, x => x.ToList()); + var usagesByCategory = usagesFlat.GroupBy(x => GetCategory(x.Category)).ToDictionary(x => x.Key, x => x.ToList()); var result = new Dictionary>(); @@ -169,7 +169,7 @@ namespace Squidex.Infrastructure.UsageTracking public async Task GetMonthlyCallsAsync(string key, DateTime date) { - Guard.NotNull(key, nameof(key)); + key = GetKey(key); ThrowIfDisposed(); @@ -181,9 +181,16 @@ namespace Squidex.Infrastructure.UsageTracking return originalUsages.Sum(x => (long)x.Counters.Get(CounterTotalCalls)); } - private static string CleanCategory(string category) + private static string GetCategory(string category) { return !string.IsNullOrWhiteSpace(category) ? category.Trim() : "*"; } + + private static string GetKey(string key) + { + Guard.NotNull(key, nameof(key)); + + return $"{key}_API"; + } } } diff --git a/src/Squidex.Infrastructure/UsageTracking/CachingUsageTracker.cs b/src/Squidex.Infrastructure/UsageTracking/CachingUsageTracker.cs index 732a48596..7998c422a 100644 --- a/src/Squidex.Infrastructure/UsageTracking/CachingUsageTracker.cs +++ b/src/Squidex.Infrastructure/UsageTracking/CachingUsageTracker.cs @@ -27,11 +27,15 @@ namespace Squidex.Infrastructure.UsageTracking public Task>> QueryAsync(string key, DateTime fromDate, DateTime toDate) { + Guard.NotNull(key, nameof(key)); + return inner.QueryAsync(key, fromDate, toDate); } public Task TrackAsync(string key, string category, double weight, double elapsedMs) { + Guard.NotNull(key, nameof(key)); + return inner.TrackAsync(key, category, weight, elapsedMs); } diff --git a/src/Squidex.Infrastructure/ValidationError.cs b/src/Squidex.Infrastructure/ValidationError.cs index 06e294aaf..5d0027514 100644 --- a/src/Squidex.Infrastructure/ValidationError.cs +++ b/src/Squidex.Infrastructure/ValidationError.cs @@ -14,7 +14,6 @@ namespace Squidex.Infrastructure [Serializable] public sealed class ValidationError { - private static readonly string[] FallbackProperties = new string[0]; private readonly string message; private readonly string[] propertyNames; @@ -34,7 +33,7 @@ namespace Squidex.Infrastructure this.message = message; - this.propertyNames = propertyNames ?? FallbackProperties; + this.propertyNames = propertyNames ?? Array.Empty(); } public ValidationError WithPrefix(string prefix) diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs index 7a6f1c579..dbff58573 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Squidex.Areas.Api.Controllers.Apps.Models; using Squidex.Domain.Apps.Entities.Apps.Commands; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Pipeline; using Squidex.Shared; @@ -45,7 +46,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [ApiCosts(0)] public IActionResult GetClients(string app) { - var response = App.Clients.Select(ClientDto.FromKvp).ToList(); + var response = App.Clients.Select(ClientDto.FromKvp).ToArray(); Response.Headers["ETag"] = App.Version.ToString(); diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs index 8ed7a7685..cf6bee081 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -46,7 +46,7 @@ namespace Squidex.Areas.Api.Controllers.Apps [ApiCosts(0)] public IActionResult GetPatterns(string app) { - var response = App.Patterns.Select(AppPatternDto.FromKvp).OrderBy(x => x.Name).ToList(); + var response = App.Patterns.Select(AppPatternDto.FromKvp).OrderBy(x => x.Name).ToArray(); Response.Headers["ETag"] = App.Version.ToString(); diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 74fc6bfb4..0f3092fb4 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -13,6 +13,7 @@ using Squidex.Areas.Api.Controllers.Apps.Models; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Services; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Security; using Squidex.Pipeline; @@ -62,7 +63,7 @@ namespace Squidex.Areas.Api.Controllers.Apps var entities = await appProvider.GetUserApps(userId, userPermissions); - var response = entities.Select(a => AppDto.FromApp(a, userId, userPermissions, appPlansProvider)).ToList(); + var response = entities.ToArray(a => AppDto.FromApp(a, userId, userPermissions, appPlansProvider)); Response.Headers["ETag"] = response.ToManyEtag(); diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppCreatedDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppCreatedDto.cs index 792296462..38e6adae1 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppCreatedDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppCreatedDto.cs @@ -8,7 +8,6 @@ using System; using System.ComponentModel.DataAnnotations; using System.Linq; -using Newtonsoft.Json; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure.Commands; diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs index dfbeec425..df40cecd5 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs @@ -9,10 +9,10 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; -using Newtonsoft.Json; using NodaTime; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Services; +using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Security; using Squidex.Pipeline; @@ -80,7 +80,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models var response = SimpleMapper.Map(app, new AppDto()); - response.Permissions = permissions.Select(x => x.Id).ToArray(); + response.Permissions = permissions.ToArray(x => x.Id); response.PlanName = plans.GetPlanForApp(app)?.Name; response.PlanUpgrade = plans.GetPlanUpgradeForApp(app)?.Name; diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs index 0a064ebac..62501e4c2 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; +using System; using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Core.Apps; @@ -34,7 +34,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// The fallback languages. /// [Required] - public List Fallback { get; set; } + public Language[] Fallback { get; set; } /// /// Indicates if the language is the master language. @@ -48,7 +48,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models public static AppLanguageDto FromCommand(AddLanguage command) { - return SimpleMapper.Map(command.Language, new AppLanguageDto { Fallback = new List() }); + return SimpleMapper.Map(command.Language, new AppLanguageDto { Fallback = Array.Empty() }); } public static AppLanguageDto[] FromApp(IAppEntity app) @@ -63,7 +63,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models { IsMaster = x == app.LanguagesConfig.Master, IsOptional = x.IsOptional, - Fallback = x.LanguageFallbacks.ToList() + Fallback = x.LanguageFallbacks.ToArray() }); } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AssignContributorDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AssignContributorDto.cs index 0efd8885b..ad429ac14 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AssignContributorDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AssignContributorDto.cs @@ -6,7 +6,6 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; -using Newtonsoft.Json; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Reflection; diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs index 4fde2f7b1..75d6b8da3 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using Newtonsoft.Json; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Reflection; diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs index 0bc35ef83..42ea190e5 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs @@ -6,7 +6,6 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; -using Newtonsoft.Json; namespace Squidex.Areas.Api.Controllers.Apps.Models { diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs index a3352dd48..8292c7f0a 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs @@ -9,6 +9,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Services; +using Squidex.Infrastructure; namespace Squidex.Areas.Api.Controllers.Apps.Models { @@ -29,7 +30,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models { var plan = plans.GetPlanForApp(app); - var contributors = app.Contributors.Select(x => new ContributorDto { ContributorId = x.Key, Role = x.Value }).ToArray(); + var contributors = app.Contributors.ToArray(x => new ContributorDto { ContributorId = x.Key, Role = x.Value }); return new ContributorsDto { Contributors = contributors, MaxContributors = plan.MaxContributors }; } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs index 4ee092e69..2c833707b 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs @@ -6,6 +6,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Core.Apps; @@ -35,7 +36,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// Associated list of permissions. /// [Required] - public string[] Permissions { get; set; } + public IEnumerable Permissions { get; set; } public static RoleDto FromRole(Role role, IAppEntity app) { @@ -46,7 +47,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models Name = role.Name, NumClients = app.Clients.Count(x => string.Equals(x.Value.Role, role.Name, StringComparison.OrdinalIgnoreCase)), NumContributors = app.Contributors.Count(x => string.Equals(x.Value, role.Name, StringComparison.OrdinalIgnoreCase)), - Permissions = permissions.ToIds().ToArray() + Permissions = permissions.ToIds() }; } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs index 8c6d2e707..0a470ff6c 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; @@ -27,7 +26,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// /// Optional fallback languages. /// - public List Fallback { get; set; } + public Language[] Fallback { get; set; } public UpdateLanguage ToCommand(Language language) { diff --git a/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs b/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs index 2f88f75f3..d69893681 100644 --- a/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Backups/BackupsController.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc; using Orleans; using Squidex.Areas.Api.Controllers.Backups.Models; using Squidex.Domain.Apps.Entities.Backup; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Tasks; using Squidex.Pipeline; @@ -53,7 +54,7 @@ namespace Squidex.Areas.Api.Controllers.Backups var jobs = await backupGrain.GetStateAsync(); - var response = jobs.Value.Select(BackupJobDto.FromBackup).ToList(); + var response = jobs.Value.ToArray(BackupJobDto.FromBackup); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Comments/Models/CommentsDto.cs b/src/Squidex/Areas/Api/Controllers/Comments/Models/CommentsDto.cs index dceed44de..063e76234 100644 --- a/src/Squidex/Areas/Api/Controllers/Comments/Models/CommentsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Comments/Models/CommentsDto.cs @@ -17,12 +17,12 @@ namespace Squidex.Areas.Api.Controllers.Comments.Models /// /// The created comments including the updates. /// - public List CreatedComments { get; set; } + public CommentDto[] CreatedComments { get; set; } /// /// The updates comments since the last version. /// - public List UpdatedComments { get; set; } + public CommentDto[] UpdatedComments { get; set; } /// /// The deleted comments since the last version. @@ -38,8 +38,8 @@ namespace Squidex.Areas.Api.Controllers.Comments.Models { return new CommentsDto { - CreatedComments = result.CreatedComments.Select(CommentDto.FromComment).ToList(), - UpdatedComments = result.UpdatedComments.Select(CommentDto.FromComment).ToList(), + CreatedComments = result.CreatedComments.Select(CommentDto.FromComment).ToArray(), + UpdatedComments = result.UpdatedComments.Select(CommentDto.FromComment).ToArray(), DeletedComments = result.DeletedComments, Version = result.Version }; diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index e53ea81b5..5a5479cbc 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.GraphQL; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Pipeline; using Squidex.Shared; diff --git a/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs b/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs index 0f770adff..f8a2c9d0d 100644 --- a/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs +++ b/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs @@ -35,7 +35,7 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers { var entities = await GetGrain().GetConsumersAsync(); - var response = entities.Value.OrderBy(x => x.Name).Select(EventConsumerDto.FromEventConsumerInfo).ToList(); + var response = entities.Value.OrderBy(x => x.Name).Select(EventConsumerDto.FromEventConsumerInfo).ToArray(); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs b/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs index 6a7565235..53e2fa8aa 100644 --- a/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs +++ b/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Squidex.Areas.Api.Controllers.History.Models; using Squidex.Domain.Apps.Entities.History; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Pipeline; using Squidex.Shared; @@ -48,7 +49,7 @@ namespace Squidex.Areas.Api.Controllers.History { var entities = await historyService.QueryByChannelAsync(AppId, channel, 100); - var response = entities.Select(HistoryEventDto.FromHistoryEvent).ToList(); + var response = entities.ToArray(HistoryEventDto.FromHistoryEvent); return Ok(response); } diff --git a/src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs b/src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs index d0750f020..8c4509e3f 100644 --- a/src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Languages/LanguagesController.cs @@ -39,7 +39,7 @@ namespace Squidex.Areas.Api.Controllers.Languages [ApiPermission] public IActionResult GetLanguages() { - var response = Language.AllLanguages.Select(LanguageDto.FromLanguage).ToList(); + var response = Language.AllLanguages.Select(LanguageDto.FromLanguage).ToArray(); Response.Headers["Etag"] = "1"; diff --git a/src/Squidex/Areas/Api/Controllers/Plans/Models/AppPlansDto.cs b/src/Squidex/Areas/Api/Controllers/Plans/Models/AppPlansDto.cs index f6130ceef..95345863e 100644 --- a/src/Squidex/Areas/Api/Controllers/Plans/Models/AppPlansDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Plans/Models/AppPlansDto.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Apps; @@ -19,7 +18,7 @@ namespace Squidex.Areas.Api.Controllers.Plans.Models /// The available plans. /// [Required] - public List Plans { get; set; } + public PlanDto[] Plans { get; set; } /// /// The current plan id. @@ -43,7 +42,7 @@ namespace Squidex.Areas.Api.Controllers.Plans.Models var response = new AppPlansDto { CurrentPlanId = planId, - Plans = plans.GetAvailablePlans().Select(PlanDto.FromPlan).ToList(), + Plans = plans.GetAvailablePlans().Select(PlanDto.FromPlan).ToArray(), PlanOwner = app.Plan?.Owner.Identifier, HasPortal = hasPortal }; diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs index 0c58dc1e9..cd75ac32b 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/Converters/RuleTriggerDtoFactory.cs @@ -33,7 +33,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Converters public RuleTriggerDto Visit(ContentChangedTrigger trigger) { - var schemas = trigger.Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchemaDto())).ToList(); + var schemas = trigger.Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchemaDto())).ToArray(); return new ContentChangedTriggerDto { Schemas = schemas, HandleAll = trigger.HandleAll }; } diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs index ef320b78d..482ac0caa 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Rules; +using Squidex.Infrastructure; namespace Squidex.Areas.Api.Controllers.Rules.Models { diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedTriggerDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedTriggerDto.cs index aa2b70386..219d42c1a 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedTriggerDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/ContentChangedTriggerDto.cs @@ -5,13 +5,12 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Generic; -using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Linq; using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Triggers; +using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Reflection; namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers @@ -23,7 +22,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers /// The schema settings. /// [Required] - public List Schemas { get; set; } + public ContentChangedTriggerSchemaDto[] Schemas { get; set; } /// /// Determines whether the trigger should handle all content changes events. @@ -32,7 +31,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers public override RuleTrigger ToTrigger() { - var schemas = Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchema())).ToImmutableList(); + var schemas = Schemas.Select(x => SimpleMapper.Map(x, new ContentChangedTriggerSchema())).ToReadOnlyCollection(); return new ContentChangedTrigger { HandleAll = HandleAll, Schemas = schemas }; } diff --git a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs index dee4249f5..e3e0da2eb 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs @@ -101,7 +101,7 @@ namespace Squidex.Areas.Api.Controllers.Rules { var entities = await appProvider.GetRulesAsync(AppId); - var response = entities.Select(RuleDto.FromRule); + var response = entities.Select(RuleDto.FromRule).ToArray(); Response.Headers["ETag"] = response.ToManyEtag(0); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs index 9750fc7c4..31bc2cc01 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Converters/FieldPropertiesDtoFactory.cs @@ -57,7 +57,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Converters public FieldPropertiesDto Visit(TagsFieldProperties properties) { - return SimpleMapper.Map(properties, new TagsFieldPropertiesDto()); + var result = SimpleMapper.Map(properties, new TagsFieldPropertiesDto()); + + result.AllowedValues = properties.AllowedValues?.ToArray(); + + return result; } public FieldPropertiesDto Visit(AssetsFieldProperties properties) diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs index e3ce42d5c..f3bdb67bd 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/AssetsFieldPropertiesDto.cs @@ -5,9 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Immutable; using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Reflection; namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields @@ -81,7 +81,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields if (AllowedExtensions != null) { - result.AllowedExtensions = ImmutableList.Create(AllowedExtensions); + result.AllowedExtensions = ReadOnlyCollection.Create(AllowedExtensions); } return result; diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/BooleanFieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/BooleanFieldPropertiesDto.cs index 45964540e..7b92fccdb 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/BooleanFieldPropertiesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/BooleanFieldPropertiesDto.cs @@ -5,8 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; @@ -29,7 +27,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// /// The editor that is used to manage this field. /// - [JsonConverter(typeof(StringEnumConverter))] public BooleanFieldEditor Editor { get; set; } public override FieldProperties ToProperties() diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs index f5f7ba515..0340f7313 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/DateTimeFieldPropertiesDto.cs @@ -5,8 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using NJsonSchema.Annotations; using NodaTime; using Squidex.Domain.Apps.Core.Schemas; @@ -35,13 +33,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// /// The editor that is used to manage this field. /// - [JsonConverter(typeof(StringEnumConverter))] public DateTimeFieldEditor Editor { get; set; } /// /// The calculated default value for the field value. /// - [JsonConverter(typeof(StringEnumConverter))] public DateTimeCalculatedDefaultValue? CalculatedDefaultValue { get; set; } public override FieldProperties ToProperties() diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/GeolocationFieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/GeolocationFieldPropertiesDto.cs index 898b2df3e..4ba929093 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/GeolocationFieldPropertiesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/GeolocationFieldPropertiesDto.cs @@ -5,8 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Reflection; @@ -24,7 +22,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// /// The editor that is used to manage this field. /// - [JsonConverter(typeof(StringEnumConverter))] public GeolocationFieldEditor Editor { get; set; } public override FieldProperties ToProperties() diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/NumberFieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/NumberFieldPropertiesDto.cs index 3a255891c..bc8c0d259 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/NumberFieldPropertiesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/NumberFieldPropertiesDto.cs @@ -5,11 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Immutable; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Reflection; namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields @@ -37,6 +35,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// public double[] AllowedValues { get; set; } + /// + /// Indicates if the field value must be unique. Ignored for nested fields and localized fields. + /// + public bool IsUnique { get; set; } + /// /// Indicates that the inline editor is enabled for this field. /// @@ -45,7 +48,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// /// The editor that is used to manage this field. /// - [JsonConverter(typeof(StringEnumConverter))] public NumberFieldEditor Editor { get; set; } public override FieldProperties ToProperties() @@ -54,7 +56,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields if (AllowedValues != null) { - result.AllowedValues = ImmutableList.Create(AllowedValues); + result.AllowedValues = ReadOnlyCollection.Create(AllowedValues); } return result; diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs index 4451846ca..bf3817561 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/StringFieldPropertiesDto.cs @@ -5,11 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Collections.Immutable; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Reflection; namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields @@ -47,6 +45,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// public string[] AllowedValues { get; set; } + /// + /// Indicates if the field value must be unique. Ignored for nested fields and localized fields. + /// + public bool IsUnique { get; set; } + /// /// Indicates that the inline editor is enabled for this field. /// @@ -55,7 +58,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// /// The editor that is used to manage this field. /// - [JsonConverter(typeof(StringEnumConverter))] public StringFieldEditor Editor { get; set; } public override FieldProperties ToProperties() @@ -64,7 +66,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields if (AllowedValues != null) { - result.AllowedValues = ImmutableList.Create(AllowedValues); + result.AllowedValues = ReadOnlyCollection.Create(AllowedValues); } return result; diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/TagsFieldPropertiesDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/TagsFieldPropertiesDto.cs index 139bbdf8f..777103f7c 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/TagsFieldPropertiesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/Fields/TagsFieldPropertiesDto.cs @@ -7,6 +7,7 @@ using NJsonSchema.Annotations; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Reflection; namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields @@ -24,9 +25,26 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models.Fields /// public int? MaxItems { get; set; } + /// + /// The allowed values for the field value. + /// + public string[] AllowedValues { get; set; } + + /// + /// The editor that is used to manage this field. + /// + public TagsFieldEditor Editor { get; set; } + public override FieldProperties ToProperties() { - return SimpleMapper.Map(this, new TagsFieldProperties()); + var result = SimpleMapper.Map(this, new TagsFieldProperties()); + + if (AllowedValues != null) + { + result.AllowedValues = ReadOnlyCollection.Create(AllowedValues); + } + + return result; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index f696a628f..86e5bdd96 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -13,6 +13,7 @@ using Squidex.Areas.Api.Controllers.Schemas.Models; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Pipeline; using Squidex.Shared; @@ -50,7 +51,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas { var schemas = await appProvider.GetSchemasAsync(AppId); - var response = schemas.Select(SchemaDto.FromSchema).ToList(); + var response = schemas.ToArray(SchemaDto.FromSchema); Response.Headers["ETag"] = response.ToManyEtag(); diff --git a/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs b/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs index 67d0295df..99065d372 100644 --- a/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc; using Squidex.Areas.Api.Controllers.Statistics.Models; using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Assets; +using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.UsageTracking; using Squidex.Pipeline; @@ -92,7 +93,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics var entities = await usageTracker.QueryAsync(AppId.ToString(), fromDate.Date, toDate.Date); - var response = entities.ToDictionary(x => x.Key, x => x.Value.Select(CallsUsageDto.FromUsage).ToList()); + var response = entities.ToDictionary(x => x.Key, x => x.Value.Select(CallsUsageDto.FromUsage).ToArray()); return Ok(response); } @@ -146,7 +147,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics var entities = await assetStatsRepository.QueryAsync(AppId, fromDate.Date, toDate.Date); - var models = entities.Select(StorageUsageDto.FromStats).ToList(); + var models = entities.Select(StorageUsageDto.FromStats).ToArray(); return Ok(models); } diff --git a/src/Squidex/Areas/Api/Controllers/UI/Models/UpdateSettingDto.cs b/src/Squidex/Areas/Api/Controllers/UI/Models/UpdateSettingDto.cs index 8262de435..b6c3f5f53 100644 --- a/src/Squidex/Areas/Api/Controllers/UI/Models/UpdateSettingDto.cs +++ b/src/Squidex/Areas/Api/Controllers/UI/Models/UpdateSettingDto.cs @@ -5,7 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json.Linq; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Areas.Api.Controllers.UI.Models { @@ -14,6 +14,6 @@ namespace Squidex.Areas.Api.Controllers.UI.Models /// /// The value for the setting. /// - public JToken Value { get; set; } + public IJsonValue Value { get; set; } } } diff --git a/src/Squidex/Areas/Api/Controllers/UI/UIController.cs b/src/Squidex/Areas/Api/Controllers/UI/UIController.cs index 385c6e20e..6d34ef4c3 100644 --- a/src/Squidex/Areas/Api/Controllers/UI/UIController.cs +++ b/src/Squidex/Areas/Api/Controllers/UI/UIController.cs @@ -14,6 +14,7 @@ using Squidex.Config; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Extensions.Actions.Twitter; using Squidex.Infrastructure.Commands; +using Squidex.Infrastructure.Orleans; using Squidex.Pipeline; namespace Squidex.Areas.Api.Controllers.UI @@ -55,9 +56,10 @@ namespace Squidex.Areas.Api.Controllers.UI { var result = await grainFactory.GetGrain(AppId).GetAsync(); - result.Value["mapType"] = uiOptions.Map?.Type ?? "OSM"; - result.Value["mapKey"] = uiOptions.Map?.GoogleMaps?.Key; - result.Value["supportTwitterAction"] = twitterOptions.IsConfigured(); + result.Value.Add("mapType", uiOptions.Map?.Type ?? "OSM"); + result.Value.Add("mapKey", uiOptions.Map?.GoogleMaps?.Key); + + result.Value.Add("supportTwitterAction", twitterOptions.IsConfigured()); return Ok(result.Value); } @@ -77,7 +79,7 @@ namespace Squidex.Areas.Api.Controllers.UI [ApiPermission] public async Task PutSetting(string app, string key, [FromBody] UpdateSettingDto request) { - await grainFactory.GetGrain(AppId).SetAsync(key, request.Value); + await grainFactory.GetGrain(AppId).SetAsync(key, request.Value.AsJ()); return NoContent(); } diff --git a/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs b/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs index bf048c0ba..8a2a5a2d4 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using Squidex.Infrastructure.Reflection; using Squidex.Shared.Users; @@ -42,11 +42,11 @@ namespace Squidex.Areas.Api.Controllers.Users.Models /// Additional permissions for the user. /// [Required] - public string[] Permissions { get; set; } + public IEnumerable Permissions { get; set; } public static UserDto FromUser(IUser user) { - var permissions = user.Permissions().ToIds().ToArray(); + var permissions = user.Permissions().ToIds(); return SimpleMapper.Map(user, new UserDto { DisplayName = user.DisplayName(), Permissions = permissions }); } diff --git a/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs b/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs index 83c57d810..85d2b032f 100644 --- a/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs +++ b/src/Squidex/Areas/Frontend/Middlewares/WebpackMiddleware.cs @@ -18,7 +18,7 @@ namespace Squidex.Areas.Frontend.Middlewares private const string Host = "localhost"; private const string Port = "3000"; private static readonly string[] Scripts = { "shims.js", "app.js" }; - private static readonly string[] Styles = new string[0]; + private static readonly string[] Styles = Array.Empty(); private readonly RequestDelegate next; public WebpackMiddleware(RequestDelegate next) diff --git a/src/Squidex/Areas/IdentityServer/Config/IdentityServerExtensions.cs b/src/Squidex/Areas/IdentityServer/Config/IdentityServerExtensions.cs index f7bc37b51..7ea0f39dc 100644 --- a/src/Squidex/Areas/IdentityServer/Config/IdentityServerExtensions.cs +++ b/src/Squidex/Areas/IdentityServer/Config/IdentityServerExtensions.cs @@ -31,12 +31,12 @@ namespace Squidex.Areas.IdentityServer.Config public static IServiceProvider UseMyAdmin(this IServiceProvider services) { - var options = services.GetService>().Value; + var options = services.GetRequiredService>().Value; - var userManager = services.GetService>(); - var userFactory = services.GetService(); + var userManager = services.GetRequiredService>(); + var userFactory = services.GetRequiredService(); - var log = services.GetService(); + var log = services.GetRequiredService(); if (options.IsAdminConfigured()) { diff --git a/src/Squidex/Areas/OrleansDashboard/Middlewares/OrleansDashboardAuthenticationMiddleware.cs b/src/Squidex/Areas/OrleansDashboard/Middlewares/OrleansDashboardAuthenticationMiddleware.cs index 08da9330f..a93e679d4 100644 --- a/src/Squidex/Areas/OrleansDashboard/Middlewares/OrleansDashboardAuthenticationMiddleware.cs +++ b/src/Squidex/Areas/OrleansDashboard/Middlewares/OrleansDashboardAuthenticationMiddleware.cs @@ -31,16 +31,23 @@ namespace Squidex.Areas.OrleansDashboard.Middlewares { var authentication = await context.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); - if (!authentication.Succeeded || !authentication.Principal.Permissions().Allows(OrleansPermissions)) + if (authentication.Succeeded) { - await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties + if (authentication.Principal.Permissions().Allows(OrleansPermissions)) { - RedirectUri = context.Request.PathBase + context.Request.Path - }); + await next(context); + } + else + { + context.Response.StatusCode = 403; + } } else { - await next(context); + await context.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties + { + RedirectUri = context.Request.PathBase + context.Request.Path + }); } } } diff --git a/src/Squidex/Config/Domain/EventPublishersServices.cs b/src/Squidex/Config/Domain/EventPublishersServices.cs index 9116f7594..dad6cd6ec 100644 --- a/src/Squidex/Config/Domain/EventPublishersServices.cs +++ b/src/Squidex/Config/Domain/EventPublishersServices.cs @@ -8,10 +8,10 @@ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Events; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; namespace Squidex.Config.Domain { @@ -54,7 +54,7 @@ namespace Squidex.Config.Domain if (enabled) { - services.AddSingletonAs(c => new RabbitMqEventConsumer(c.GetRequiredService(), name, publisherConfig, exchange, eventsFilter)) + services.AddSingletonAs(c => new RabbitMqEventConsumer(c.GetRequiredService(), name, publisherConfig, exchange, eventsFilter)) .As(); } } diff --git a/src/Squidex/Config/Domain/EventStoreServices.cs b/src/Squidex/Config/Domain/EventStoreServices.cs index 85066ae53..a960b1488 100644 --- a/src/Squidex/Config/Domain/EventStoreServices.cs +++ b/src/Squidex/Config/Domain/EventStoreServices.cs @@ -14,6 +14,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing.Grains; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.States; namespace Squidex.Config.Domain @@ -50,7 +51,7 @@ namespace Squidex.Config.Domain services.AddSingletonAs(c => new GetEventStoreHealthCheck(connection)) .As(); - services.AddSingletonAs(c => new GetEventStore(connection, eventStorePrefix, eventStoreProjectionHost)) + services.AddSingletonAs(c => new GetEventStore(connection, c.GetRequiredService(), eventStorePrefix, eventStoreProjectionHost)) .As(); } }); diff --git a/src/Squidex/Config/Domain/InfrastructureServices.cs b/src/Squidex/Config/Domain/InfrastructureServices.cs index 3c2b245c5..bf8d71575 100644 --- a/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -54,12 +54,12 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - services.AddSingletonAs() - .As(); - services.AddSingletonAs() .As(); + services.AddSingletonAs() + .As(); + services.AddTransient(typeof(Lazy<>), typeof(Lazier<>)); } } diff --git a/src/Squidex/Config/Domain/LoggingExtensions.cs b/src/Squidex/Config/Domain/LoggingExtensions.cs index 0ee6c0844..1c1526682 100644 --- a/src/Squidex/Config/Domain/LoggingExtensions.cs +++ b/src/Squidex/Config/Domain/LoggingExtensions.cs @@ -28,11 +28,13 @@ namespace Squidex.Config.Domain var logged = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (var kvp in config.AsEnumerable().Where(kvp => kvp.Value != null).Select(x => new { Key = x.Key.ToLowerInvariant(), x.Value }).OrderBy(x => x.Key)) + var orderedConfigs = config.AsEnumerable().Where(kvp => kvp.Value != null).OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase); + + foreach (var kvp in orderedConfigs) { if (logged.Add(kvp.Key)) { - c.WriteProperty(kvp.Key, kvp.Value); + c.WriteProperty(kvp.Key.ToLowerInvariant(), kvp.Value); } } })); diff --git a/src/Squidex/Config/Domain/LoggingServices.cs b/src/Squidex/Config/Domain/LoggingServices.cs index a00ad2db2..0d1631ded 100644 --- a/src/Squidex/Config/Domain/LoggingServices.cs +++ b/src/Squidex/Config/Domain/LoggingServices.cs @@ -8,7 +8,6 @@ using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; using Squidex.Infrastructure.Log; using Squidex.Pipeline; @@ -25,11 +24,13 @@ namespace Squidex.Config.Domain { if (config.GetValue("logging:human")) { - services.AddSingletonAs(c => new Func(() => new JsonLogWriter(Formatting.Indented, true))); + services.AddSingletonAs(JsonLogWriterFactory.Readable()) + .As(); } else { - services.AddSingletonAs(c => new Func(() => new JsonLogWriter())); + services.AddSingletonAs(JsonLogWriterFactory.Default()) + .As(); } var loggingFile = config.GetValue("logging:file"); diff --git a/src/Squidex/Config/Domain/SerializationServices.cs b/src/Squidex/Config/Domain/SerializationServices.cs index c52abca55..87916c8ac 100644 --- a/src/Squidex/Config/Domain/SerializationServices.cs +++ b/src/Squidex/Config/Domain/SerializationServices.cs @@ -9,8 +9,6 @@ using Microsoft.Extensions.DependencyInjection; using Migrate_01; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using NodaTime; -using NodaTime.Serialization.JsonNet; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps.Json; using Squidex.Domain.Apps.Core.Rules.Json; @@ -19,8 +17,8 @@ using Squidex.Domain.Apps.Core.Schemas.Json; using Squidex.Domain.Apps.Events; using Squidex.Extensions.Actions; using Squidex.Infrastructure; -using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Newtonsoft; namespace Squidex.Config.Domain { @@ -48,18 +46,18 @@ namespace Squidex.Config.Domain new AppContributorsConverter(), new AppPatternsConverter(), new ClaimsPrincipalConverter(), + new EnvelopeHeadersConverter(), new InstantConverter(), + new JsonValueConverter(), new LanguageConverter(), new LanguagesConfigConverter(), new NamedGuidIdConverter(), new NamedLongIdConverter(), new NamedStringIdConverter(), - new PropertiesBagConverter(), - new PropertiesBagConverter(), new RefTokenConverter(), new RolesConverter(), new RuleConverter(), - new SchemaConverter(FieldRegistry), + new SchemaConverter(), new StringEnumConverter()); settings.NullValueHandling = NullValueHandling.Ignore; @@ -68,8 +66,6 @@ namespace Squidex.Config.Domain settings.DateParseHandling = DateParseHandling.None; settings.TypeNameHandling = typeNameHandling; - - settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); } static SerializationServices() @@ -86,6 +82,8 @@ namespace Squidex.Config.Domain services.AddSingleton(DefaultJsonSerializer); services.AddSingleton(TypeNameRegistry); + services.AddSingleton(new NewtonsoftJsonSerializer(DefaultJsonSettings)); + return services; } diff --git a/src/Squidex/Config/Domain/StoreServices.cs b/src/Squidex/Config/Domain/StoreServices.cs index eb5c8065f..0eb9fe2f6 100644 --- a/src/Squidex/Config/Domain/StoreServices.cs +++ b/src/Squidex/Config/Domain/StoreServices.cs @@ -29,6 +29,7 @@ using Squidex.Domain.Users.MongoDb.Infrastructure; using Squidex.Infrastructure; using Squidex.Infrastructure.Diagnostics; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Migrations; using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.States; @@ -65,8 +66,14 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - services.AddSingletonAs() - .As(); + services.AddTransientAs() + .As(); + + services.AddTransientAs() + .As(); + + services.AddTransientAs(c => new DeleteContentCollections(mongoContentDatabase)) + .As(); services.AddSingletonAs() .As(); @@ -74,12 +81,15 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - services.AddSingletonAs() - .As>(); - services.AddSingletonAs() .As(); + services.AddSingletonAs() + .As(); + + services.AddSingletonAs() + .As>(); + services.AddSingletonAs() .As>() .As(); @@ -88,16 +98,10 @@ namespace Squidex.Config.Domain .As() .As>(); - services.AddSingletonAs(c => new MongoContentRepository(mongoContentDatabase, c.GetService())) + services.AddSingletonAs(c => new MongoContentRepository(mongoContentDatabase, c.GetRequiredService(), c.GetRequiredService())) .As() .As>() .As(); - - services.AddTransientAs() - .As(); - - services.AddTransientAs(c => new DeleteContentCollections(mongoContentDatabase)) - .As(); } }); diff --git a/src/Squidex/Config/Domain/SubscriptionServices.cs b/src/Squidex/Config/Domain/SubscriptionServices.cs index a9205dbeb..10437c2b7 100644 --- a/src/Squidex/Config/Domain/SubscriptionServices.cs +++ b/src/Squidex/Config/Domain/SubscriptionServices.cs @@ -19,7 +19,7 @@ namespace Squidex.Config.Domain { public static void AddMySubscriptionServices(this IServiceCollection services, IConfiguration config) { - services.AddSingletonAs(c => c.GetService>()?.Value?.Plans.OrEmpty()); + services.AddSingletonAs(c => c.GetRequiredService>()?.Value?.Plans.OrEmpty()); services.AddSingletonAs() .As(); diff --git a/src/Squidex/Config/Web/WebExtensions.cs b/src/Squidex/Config/Web/WebExtensions.cs index fa649cab3..82a71d289 100644 --- a/src/Squidex/Config/Web/WebExtensions.cs +++ b/src/Squidex/Config/Web/WebExtensions.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HttpOverrides; +using Squidex.Infrastructure.Diagnostics; using Squidex.Pipeline; using Squidex.Pipeline.Diagnostics; using Squidex.Pipeline.Robots; @@ -31,7 +32,20 @@ namespace Squidex.Config.Web public static IApplicationBuilder UseMyHealthCheck(this IApplicationBuilder app) { - app.Map("/healthz", builder => builder.UseMiddleware()); + app.Map("/readiness", builder => + { + builder.UseMiddleware(HealthCheckScopes.Any); + }); + + app.Map("/healthz", builder => + { + builder.UseMiddleware(HealthCheckScopes.Node); + }); + + app.Map("/cluster-healthz", builder => + { + builder.UseMiddleware(HealthCheckScopes.Cluster); + }); return app; } diff --git a/src/Squidex/Pipeline/ApiExceptionFilterAttribute.cs b/src/Squidex/Pipeline/ApiExceptionFilterAttribute.cs index 62aa39efe..6d0294fca 100644 --- a/src/Squidex/Pipeline/ApiExceptionFilterAttribute.cs +++ b/src/Squidex/Pipeline/ApiExceptionFilterAttribute.cs @@ -61,7 +61,7 @@ namespace Squidex.Pipeline private static IActionResult OnValidationException(ValidationException ex) { - return ErrorResult(400, new ErrorDto { Message = ex.Summary, Details = ex.Errors?.Select(e => e.Message).ToArray() }); + return ErrorResult(400, new ErrorDto { Message = ex.Summary, Details = ex.Errors?.ToArray(e => e.Message) }); } private static IActionResult ErrorResult(int statusCode, ErrorDto error) diff --git a/src/Squidex/Pipeline/Diagnostics/HealthCheckMiddleware.cs b/src/Squidex/Pipeline/Diagnostics/HealthCheckMiddleware.cs index 11eec2ded..00725377d 100644 --- a/src/Squidex/Pipeline/Diagnostics/HealthCheckMiddleware.cs +++ b/src/Squidex/Pipeline/Diagnostics/HealthCheckMiddleware.cs @@ -11,48 +11,56 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; using Squidex.Infrastructure; using Squidex.Infrastructure.Diagnostics; +using Squidex.Infrastructure.Json; namespace Squidex.Pipeline.Diagnostics { - public sealed class HealthCheckMiddleware : IMiddleware + public sealed class HealthCheckMiddleware { private const string Suffix = "HealthCheck"; private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2); private readonly Dictionary healthChecks; - private readonly JsonSerializerSettings serializerSettings; + private readonly IJsonSerializer serializer; + private readonly RequestDelegate next; + private readonly List scopes; - public HealthCheckMiddleware(IEnumerable healthChecks, JsonSerializerSettings serializerSettings) + public HealthCheckMiddleware(IEnumerable healthChecks, IJsonSerializer serializer, RequestDelegate next, string scopes) { Guard.NotNull(healthChecks, nameof(healthChecks)); - Guard.NotNull(serializerSettings, nameof(serializerSettings)); + Guard.NotNull(serializer, nameof(serializer)); this.healthChecks = healthChecks.ToDictionary(GetName); - this.serializerSettings = serializerSettings; + this.next = next; + this.serializer = serializer; + this.scopes = SplitScopes(scopes); } - public async Task InvokeAsync(HttpContext context, RequestDelegate next) + public async Task Invoke(HttpContext context) { if (CanServeRequest(context.Request)) { using (var cts = new CancellationTokenSource(Timeout)) { - var checks = await Task.WhenAll(healthChecks.Select(x => MakeHealthCheckAsync(x.Key, x.Value, cts.Token))); + var matchingChecks = healthChecks.Where(x => CanUseCheck(x.Value)); + + var results = await Task.WhenAll(matchingChecks.Select(x => MakeHealthCheckAsync(x.Key, x.Value, cts.Token))); context.Response.StatusCode = 200; context.Response.Headers.Add("Content-Type", "application/json"); - if (checks.Any(x => !x.Result.IsHealthy)) + if (results.Any(x => !x.Result.IsHealthy)) { context.Response.StatusCode = 503; } - var response = checks.ToDictionary(x => x.Name, x => x.Result); + var response = results.ToDictionary(x => x.Name, x => x.Result); + + var json = serializer.Serialize(new { status = response }); - await context.Response.WriteAsync(JsonConvert.SerializeObject(new { status = response }, Formatting.Indented, serializerSettings)); + await context.Response.WriteAsync(json); } } else @@ -61,11 +69,21 @@ namespace Squidex.Pipeline.Diagnostics } } - private static bool CanServeRequest(HttpRequest request) + private bool CanUseCheck(IHealthCheck check) + { + return scopes.Count == 0 || check.Scopes.Intersect(scopes).Any(); + } + + private bool CanServeRequest(HttpRequest request) { return HttpMethods.IsGet(request.Method) && (request.Path == "/" || string.IsNullOrEmpty(request.Path)); } + private static List SplitScopes(string scopes) + { + return scopes.Split(",").Where(x => x != "*").ToList(); + } + private static string GetName(IHealthCheck check) { var name = check.GetType().Name.ToCamelCase(); diff --git a/src/Squidex/Pipeline/ETagExtensions.cs b/src/Squidex/Pipeline/ETagExtensions.cs index 4c30e2411..074c952a3 100644 --- a/src/Squidex/Pipeline/ETagExtensions.cs +++ b/src/Squidex/Pipeline/ETagExtensions.cs @@ -5,8 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Collections.Generic; -using System.Linq; +using System.Text; using Squidex.Infrastructure; using Squidex.Infrastructure.Log; @@ -14,17 +15,59 @@ namespace Squidex.Pipeline { public static class ETagExtensions { - public static string ToManyEtag(this IEnumerable items, long total = 0) where T : IGenerateEtag + private static readonly int GuidLength = Guid.Empty.ToString().Length; + + public static string ToManyEtag(this IReadOnlyList items, long total = 0) where T : IGenerateEtag { using (Profiler.Trace("CalculateEtag")) { - return $"{total}_{string.Join(";", items.Select(x => $"{x.Id}{x.Version}"))}".Sha256Base64(); + var unhashed = Unhashed(items, total); + + return unhashed.Sha256Base64(); } } - public static string ToSurrogateKeys(this IEnumerable items) where T : IGenerateEtag + private static string Unhashed(IReadOnlyList items, long total) where T : IGenerateEtag { - return string.Join(" ", items.Select(x => x.Id)); + var sb = new StringBuilder((items.Count * (GuidLength + 4)) + 10); + + sb.Append(total); + sb.Append("_"); + + if (items.Count > 0) + { + sb.Append(items[0].Id.ToString()); + sb.Append(items[0].Version); + + for (var i = 1; i < items.Count; i++) + { + sb.Append(";"); + sb.Append(items[i].Id.ToString()); + sb.Append(items[i].Version); + } + } + + return sb.ToString(); + } + + public static string ToSurrogateKeys(this IReadOnlyList items) where T : IGenerateEtag + { + if (items.Count == 0) + { + return string.Empty; + } + + var sb = new StringBuilder(items.Count * (GuidLength + 1)); + + sb.Append(items[0].Id.ToString()); + + for (var i = 1; i < items.Count; i++) + { + sb.Append(" "); + sb.Append(items[i].Id.ToString()); + } + + return sb.ToString(); } public static string ToEtag(this T item) where T : IGenerateEtag diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index 920536347..52bd714c8 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -80,7 +80,6 @@ - diff --git a/src/Squidex/app/features/content/shared/array-editor.component.html b/src/Squidex/app/features/content/shared/array-editor.component.html index d881c1d6d..5d898d963 100644 --- a/src/Squidex/app/features/content/shared/array-editor.component.html +++ b/src/Squidex/app/features/content/shared/array-editor.component.html @@ -5,15 +5,21 @@ - \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/array-editor.component.ts b/src/Squidex/app/features/content/shared/array-editor.component.ts index 4877c97db..64c957e14 100644 --- a/src/Squidex/app/features/content/shared/array-editor.component.ts +++ b/src/Squidex/app/features/content/shared/array-editor.component.ts @@ -6,7 +6,7 @@ */ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { AbstractControl, FormArray } from '@angular/forms'; +import { AbstractControl, FormArray, FormGroup } from '@angular/forms'; import { AppLanguageDto, @@ -37,12 +37,18 @@ export class ArrayEditorComponent { @Input() public arrayControl: FormArray; + public isHidden = false; + + public hide(hide: boolean) { + this.isHidden = hide; + } + public removeItem(index: number) { this.form.removeArrayItem(this.field, this.language, index); } - public addItem() { - this.form.insertArrayItem(this.field, this.language); + public addItem(value?: FormGroup) { + this.form.insertArrayItem(this.field, this.language, value); } public sort(controls: AbstractControl[]) { @@ -50,4 +56,13 @@ export class ArrayEditorComponent { this.arrayControl.setControl(i, controls[i]); } } + + public move(control: AbstractControl, index: number) { + let controls = [...this.arrayControl.controls]; + + controls.splice(controls.indexOf(control), 1); + controls.splice(index, 0, control); + + this.sort(controls); + } } \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/array-item.component.html b/src/Squidex/app/features/content/shared/array-item.component.html index 337033cfa..baf0868c1 100644 --- a/src/Squidex/app/features/content/shared/array-item.component.html +++ b/src/Squidex/app/features/content/shared/array-item.component.html @@ -4,14 +4,40 @@ Item #{{index + 1}} + + + + + + + - + + + + + + -
+
(); + + @Output() + public cloning = new EventEmitter(); + + @Output() + public toggle = new EventEmitter(); + @Input() public form: EditContentForm; @Input() public field: RootFieldDto; + @Input() + public isHidden = false; + + @Input() + public isFirst = false; + + @Input() + public isLast = false; + @Input() public index: number; @@ -59,4 +77,20 @@ export class ArrayItemComponent implements OnChanges { this.fieldControls = this.field.nested.map(field => ({ field, control: this.itemForm.get(field.name)! })).filter(x => !!x.control); } } + + public moveTop() { + this.moving.emit(0); + } + + public moveUp() { + this.moving.emit(this.index - 1); + } + + public moveDown() { + this.moving.emit(this.index + 1); + } + + public moveBottom() { + this.moving.emit(99999); + } } \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/field-editor.component.html b/src/Squidex/app/features/content/shared/field-editor.component.html index abdffb13b..839cc3d84 100644 --- a/src/Squidex/app/features/content/shared/field-editor.component.html +++ b/src/Squidex/app/features/content/shared/field-editor.component.html @@ -104,7 +104,19 @@ - + + + + + + + + + + + +
+
+
+ + +
+
+
+
diff --git a/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.ts index 18cd9e1bf..489010653 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/types/number-validation.component.ts @@ -10,7 +10,12 @@ import { FormControl, FormGroup } from '@angular/forms'; import { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; -import { FieldDto, NumberFieldPropertiesDto } from '@app/shared'; +import { + FieldDto, + NumberFieldPropertiesDto, + RootFieldDto, + Types +} from '@app/shared'; @Component({ selector: 'sqx-number-validation', @@ -27,9 +32,18 @@ export class NumberValidationComponent implements OnInit { @Input() public properties: NumberFieldPropertiesDto; + public showUnique: boolean; + public showDefaultValue: Observable; public ngOnInit() { + this.showUnique = Types.is(this.field, RootFieldDto) && !this.field.isLocalizable; + + if (this.showUnique) { + this.editForm.setControl('isUnique', + new FormControl(this.properties.isUnique)); + } + this.editForm.setControl('maxValue', new FormControl(this.properties.maxValue)); diff --git a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html index bf6e7b214..b81865ed6 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html @@ -1,4 +1,15 @@
+
+
+
+ + +
+
+
+
diff --git a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts index 22f64ffaa..3f1c137a0 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts @@ -15,7 +15,9 @@ import { FieldDto, ImmutableArray, ModalModel, - StringFieldPropertiesDto + RootFieldDto, + StringFieldPropertiesDto, + Types } from '@app/shared'; @Component({ @@ -45,11 +47,20 @@ export class StringValidationComponent implements OnDestroy, OnInit { public patternName: string; public patternsModal = new ModalModel(); + public showUnique: boolean; + public ngOnDestroy() { this.patternSubscription.unsubscribe(); } public ngOnInit() { + this.showUnique = Types.is(this.field, RootFieldDto) && !this.field.isLocalizable; + + if (this.showUnique) { + this.editForm.setControl('isUnique', + new FormControl(this.properties.isUnique)); + } + this.editForm.setControl('maxLength', new FormControl(this.properties.maxLength)); diff --git a/src/Squidex/app/features/schemas/pages/schema/types/tags-ui.component.html b/src/Squidex/app/features/schemas/pages/schema/types/tags-ui.component.html index 9a0e7afd1..750a2187a 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/tags-ui.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/types/tags-ui.component.html @@ -1,3 +1,58 @@
- Nothing to setup -
\ No newline at end of file +
+ + +
+ + + + Url to your plugin if you use a custom editor. + +
+
+
+ + +
+ + + + Define the placeholder for the input control. + +
+
+
+ + +
+ + + +
+
+
+ + +
+ +
+
+
\ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/types/tags-ui.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/tags-ui.component.ts index 68dadab87..260698455 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/tags-ui.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/types/tags-ui.component.ts @@ -5,17 +5,17 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, Input } from '@angular/core'; -import { FormGroup } from '@angular/forms'; +import { Component, Input, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { AssetsFieldPropertiesDto, FieldDto } from '@app/shared'; +import { FieldDto, TagsFieldPropertiesDto } from '@app/shared'; @Component({ selector: 'sqx-tags-ui', styleUrls: ['tags-ui.component.scss'], templateUrl: 'tags-ui.component.html' }) -export class TagsUIComponent { +export class TagsUIComponent implements OnInit { @Input() public editForm: FormGroup; @@ -23,5 +23,15 @@ export class TagsUIComponent { public field: FieldDto; @Input() - public properties: AssetsFieldPropertiesDto; + public properties: TagsFieldPropertiesDto; + + public ngOnInit() { + this.editForm.setControl('editor', + new FormControl(this.properties.editor, [ + Validators.required + ])); + + this.editForm.setControl('allowedValues', + new FormControl(this.properties.allowedValues)); + } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/checkbox-group.component.html b/src/Squidex/app/framework/angular/forms/checkbox-group.component.html new file mode 100644 index 000000000..0f2a18bc1 --- /dev/null +++ b/src/Squidex/app/framework/angular/forms/checkbox-group.component.html @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/checkbox-group.component.scss b/src/Squidex/app/framework/angular/forms/checkbox-group.component.scss new file mode 100644 index 000000000..c30feb8f0 --- /dev/null +++ b/src/Squidex/app/framework/angular/forms/checkbox-group.component.scss @@ -0,0 +1,12 @@ +@import '_mixins'; +@import '_vars'; + +.form-check { + display: inline-block; + margin-left: 0; + margin-right: 1rem; +} + +.form-check-input { + margin-top: .4rem; +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/checkbox-group.component.ts b/src/Squidex/app/framework/angular/forms/checkbox-group.component.ts new file mode 100644 index 000000000..a8d3b4ac3 --- /dev/null +++ b/src/Squidex/app/framework/angular/forms/checkbox-group.component.ts @@ -0,0 +1,79 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; + +import { Types } from '@app/framework/internal'; +import { MathHelper } from '@app/shared'; + +export const SQX_CHECKBOX_GROUP_CONTROL_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CheckboxGroupComponent), multi: true +}; + +@Component({ + selector: 'sqx-checkbox-group', + styleUrls: ['./checkbox-group.component.scss'], + templateUrl: './checkbox-group.component.html', + providers: [SQX_CHECKBOX_GROUP_CONTROL_VALUE_ACCESSOR], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CheckboxGroupComponent implements ControlValueAccessor { + private callChange = (v: any) => { /* NOOP */ }; + private callTouched = () => { /* NOOP */ }; + private checkedValues: string[] = []; + + @Input() + public values: string[] = []; + + public isDisabled = false; + + public control = MathHelper.guid(); + + constructor( + private readonly changeDetector: ChangeDetectorRef + ) { + } + + public writeValue(obj: any) { + this.checkedValues = Types.isArrayOfString(obj) ? obj.filter(x => this.values.indexOf(x) >= 0) : []; + + this.changeDetector.markForCheck(); + } + + public setDisabledState(isDisabled: boolean): void { + this.isDisabled = isDisabled; + + this.changeDetector.markForCheck(); + } + + public registerOnChange(fn: any) { + this.callChange = fn; + } + + public registerOnTouched(fn: any) { + this.callTouched = fn; + } + + public blur() { + this.callTouched(); + } + + public check(isChecked: boolean, value: string) { + if (isChecked) { + this.checkedValues = [value, ...this.checkedValues]; + } else { + this.checkedValues = this.checkedValues.filter(x => x !== value); + } + + this.callChange(this.checkedValues); + } + + public isChecked(value: string) { + return this.checkedValues.indexOf(value) >= 0; + } +} diff --git a/src/Squidex/app/framework/angular/forms/control-errors.component.ts b/src/Squidex/app/framework/angular/forms/control-errors.component.ts index e31f1f901..caeb9e437 100644 --- a/src/Squidex/app/framework/angular/forms/control-errors.component.ts +++ b/src/Squidex/app/framework/angular/forms/control-errors.component.ts @@ -22,7 +22,8 @@ const DEFAULT_ERRORS: { [key: string]: string } = { match: '{message}', validdatetime: '{field} is not a valid date time', validnumber: '{field} is not a valid number.', - validvalues: '{field} is not a valid value.' + validvalues: '{field} is not a valid value.', + validarrayvalues: '{field} contains an invalid value.' }; @Component({ diff --git a/src/Squidex/app/framework/angular/forms/dropdown.component.html b/src/Squidex/app/framework/angular/forms/dropdown.component.html deleted file mode 100644 index 7659f0b7c..000000000 --- a/src/Squidex/app/framework/angular/forms/dropdown.component.html +++ /dev/null @@ -1,26 +0,0 @@ - -
- - -
- {{selectedItem}} - - -
- - -
- -
-
-
- {{item}} - - -
-
-
-
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/dropdown.component.scss b/src/Squidex/app/framework/angular/forms/dropdown.component.scss deleted file mode 100644 index 2b7ec23c4..000000000 --- a/src/Squidex/app/framework/angular/forms/dropdown.component.scss +++ /dev/null @@ -1,38 +0,0 @@ -@import '_mixins'; -@import '_vars'; - -$color-input-disabled: #eef1f4; - -.form-control { - & { - width: 100%; - } - - &[readonly] { - background: $color-input-background; - } - - &:disabled { - background: $color-input-disabled; - } -} - -.selection { - & { - position: relative; - overflow: hidden; - } - - .control-dropdown-item { - @include absolute(0, 1rem, 0, 0); - pointer-events: none; - } - - .icon-caret-down { - @include absolute(30%, .4rem, auto, auto); - cursor: pointer; - font-size: .9rem; - font-weight: normal; - pointer-events: none; - } -} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/dropdown.component.ts b/src/Squidex/app/framework/angular/forms/dropdown.component.ts deleted file mode 100644 index 1547138f1..000000000 --- a/src/Squidex/app/framework/angular/forms/dropdown.component.ts +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; - -const KEY_ENTER = 13; -const KEY_ESCAPE = 27; -const KEY_UP = 38; -const KEY_DOWN = 40; - -import { ModalModel } from '@app/framework/internal'; - -export const SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = { - provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DropdownComponent), multi: true -}; - -@Component({ - selector: 'sqx-dropdown', - styleUrls: ['./dropdown.component.scss'], - templateUrl: './dropdown.component.html', - providers: [SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class DropdownComponent implements AfterContentInit, ControlValueAccessor { - private callChange = (v: any) => { /* NOOP */ }; - private callTouched = () => { /* NOOP */ }; - - @Input() - public items: any[] = []; - - @ContentChildren(TemplateRef) - public templates: QueryList; - - public dropdown = new ModalModel(); - - public selectedItem: any; - public selectedIndex = -1; - public selectionTemplate: TemplateRef; - - public itemTemplate: TemplateRef; - - public isDisabled = false; - - constructor( - private readonly changeDetector: ChangeDetectorRef - ) { - } - - public ngAfterContentInit() { - if (this.templates.length === 1) { - this.itemTemplate = this.selectionTemplate = this.templates.first; - } else { - this.templates.forEach(template => { - if (template.name === 'selection') { - this.selectionTemplate = template; - } else { - this.itemTemplate = template; - } - }); - } - } - - public writeValue(obj: any) { - this.selectIndex(this.items && obj ? this.items.indexOf(obj) : 0); - - this.changeDetector.markForCheck(); - } - - public setDisabledState(isDisabled: boolean): void { - this.isDisabled = isDisabled; - - this.changeDetector.markForCheck(); - } - - public registerOnChange(fn: any) { - this.callChange = fn; - } - - public registerOnTouched(fn: any) { - this.callTouched = fn; - } - - public onKeyDown(event: KeyboardEvent) { - switch (event.keyCode) { - case KEY_UP: - this.up(); - return false; - case KEY_DOWN: - this.down(); - return false; - case KEY_ESCAPE: - case KEY_ENTER: - if (this.dropdown.isOpen) { - this.close(); - return false; - } - } - - return true; - } - - public open() { - this.dropdown.show(); - this.callTouched(); - } - - public selectIndexAndClose(selectedIndex: number) { - this.selectIndex(selectedIndex); - this.close(); - } - - private close() { - this.dropdown.hide(); - } - - private up() { - this.selectIndex(this.selectedIndex - 1); - } - - private down() { - this.selectIndex(this.selectedIndex + 1); - } - - private selectIndex(selectedIndex: number) { - if (selectedIndex < 0) { - selectedIndex = 0; - } - - const items = this.items || []; - - if (selectedIndex >= items.length) { - selectedIndex = items.length - 1; - } - - const value = items[selectedIndex]; - - if (value !== this.selectedItem) { - this.selectedIndex = selectedIndex; - this.selectedItem = value; - - this.callChange(value); - } - } -} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/validators.spec.ts b/src/Squidex/app/framework/angular/forms/validators.spec.ts index 3624fb416..5a9126443 100644 --- a/src/Squidex/app/framework/angular/forms/validators.spec.ts +++ b/src/Squidex/app/framework/angular/forms/validators.spec.ts @@ -101,6 +101,30 @@ describe('ValidatorsEx.validValues', () => { }); }); +describe('ValidatorsEx.validArrayValues', () => { + it('should return null validator if values not defined', () => { + const validator = ValidatorsEx.validArrayValues(null!); + + expect(validator).toBe(Validators.nullValidator); + }); + + it('should return null if value is in allowed values', () => { + const input = new FormControl([10, 20]); + + const error = ValidatorsEx.validArrayValues([10, 20, 30])(input); + + expect(error).toBeNull(); + }); + + it('should return error if value is not in allowed values', () => { + const input = new FormControl([50]); + + const error = ValidatorsEx.validArrayValues([10, 20, 30])(input); + + expect(error.validarrayvalues).toBeDefined(); + }); +}); + describe('ValidatorsEx.match', () => { it('should revalidate if other control changes', () => { const validator = ValidatorsEx.match('password', 'Passwords are not the same.'); diff --git a/src/Squidex/app/framework/angular/forms/validators.ts b/src/Squidex/app/framework/angular/forms/validators.ts index 3bbd8831f..32c6557bf 100644 --- a/src/Squidex/app/framework/angular/forms/validators.ts +++ b/src/Squidex/app/framework/angular/forms/validators.ts @@ -125,6 +125,26 @@ export module ValidatorsEx { }; } + export function validArrayValues(values: T[]): ValidatorFn { + if (!values) { + return Validators.nullValidator; + } + + return (control: AbstractControl) => { + const ns: T[] = control.value; + + if (ns) { + for (let n of ns) { + if (values.indexOf(n) < 0) { + return { validarrayvalues: false }; + } + } + } + + return null; + }; + } + export function noop(): ValidatorFn { return () => { return null; diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index 8537ff51c..5c2e813be 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -6,12 +6,12 @@ */ export * from './angular/forms/autocomplete.component'; +export * from './angular/forms/checkbox-group.component'; export * from './angular/forms/code-editor.component'; export * from './angular/forms/confirm-click.directive'; export * from './angular/forms/control-errors.component'; export * from './angular/forms/copy.directive'; export * from './angular/forms/date-time-editor.component'; -export * from './angular/forms/dropdown.component'; export * from './angular/forms/file-drop.directive'; export * from './angular/forms/focus-on-init.directive'; export * from './angular/forms/form-error.component'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 8e4d0404d..afaa033b4 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -15,6 +15,7 @@ import { AutocompleteComponent, CachingInterceptor, CanDeactivateGuard, + CheckboxGroupComponent, ClipboardService, CodeEditorComponent, ConfirmClickDirective, @@ -28,7 +29,6 @@ import { DialogRendererComponent, DialogService, DisplayNamePipe, - DropdownComponent, DurationPipe, FileDropDirective, FileSizePipe, @@ -92,6 +92,7 @@ import { ], declarations: [ AutocompleteComponent, + CheckboxGroupComponent, ConfirmClickDirective, ControlErrorsComponent, CodeEditorComponent, @@ -103,7 +104,6 @@ import { DayPipe, DialogRendererComponent, DisplayNamePipe, - DropdownComponent, DurationPipe, FileDropDirective, FileSizePipe, @@ -152,6 +152,7 @@ import { ], exports: [ AutocompleteComponent, + CheckboxGroupComponent, CodeEditorComponent, CommonModule, ConfirmClickDirective, @@ -164,7 +165,6 @@ import { DayPipe, DialogRendererComponent, DisplayNamePipe, - DropdownComponent, DurationPipe, FileDropDirective, FileSizePipe, diff --git a/src/Squidex/app/shared/services/schemas.types.ts b/src/Squidex/app/shared/services/schemas.types.ts index 17f759351..5e47756ba 100644 --- a/src/Squidex/app/shared/services/schemas.types.ts +++ b/src/Squidex/app/shared/services/schemas.types.ts @@ -73,7 +73,7 @@ export function createProperties(fieldType: string, values: Object | null = null properties = new StringFieldPropertiesDto('Input'); break; case 'Tags': - properties = new TagsFieldPropertiesDto(); + properties = new TagsFieldPropertiesDto('Tags'); break; default: throw 'Invalid properties type'; @@ -241,6 +241,7 @@ export class NumberFieldPropertiesDto extends FieldPropertiesDto { public readonly fieldType = 'Number'; public readonly inlineEditable: boolean = false; + public readonly isUnique: boolean = false; public readonly defaultValue?: number; public readonly maxValue?: number; public readonly minValue?: number; @@ -279,6 +280,7 @@ export class StringFieldPropertiesDto extends FieldPropertiesDto { public readonly fieldType = 'String'; public readonly inlineEditable = false; + public readonly isUnique: boolean = false; public readonly defaultValue?: string; public readonly pattern?: string; public readonly patternMessage?: string; @@ -302,11 +304,12 @@ export class TagsFieldPropertiesDto extends FieldPropertiesDto { public readonly minItems?: number; public readonly maxItems?: number; + public readonly allowedValues?: string[]; - constructor( + constructor(editor: string, props?: Partial ) { - super('Default', props); + super('Tags', props); } public accept(visitor: FieldPropertiesVisitor): T { diff --git a/src/Squidex/app/shared/state/contents.forms.spec.ts b/src/Squidex/app/shared/state/contents.forms.spec.ts index 61da6789d..2a72eab7d 100644 --- a/src/Squidex/app/shared/state/contents.forms.spec.ts +++ b/src/Squidex/app/shared/state/contents.forms.spec.ts @@ -166,7 +166,7 @@ describe('AssetsField', () => { }); describe('TagsField', () => { - const field = createField(new TagsFieldPropertiesDto({ isRequired: true, minItems: 1, maxItems: 5 })); + const field = createField(new TagsFieldPropertiesDto('Tags', { isRequired: true, minItems: 1, maxItems: 5 })); it('should create validators', () => { expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(3); diff --git a/src/Squidex/app/shared/state/contents.forms.ts b/src/Squidex/app/shared/state/contents.forms.ts index 74cb686d7..932a85fcc 100644 --- a/src/Squidex/app/shared/state/contents.forms.ts +++ b/src/Squidex/app/shared/state/contents.forms.ts @@ -250,6 +250,12 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor 0) { + const values: (string | null)[] = properties.allowedValues; + + validators.push(ValidatorsEx.validArrayValues(values)); + } + return validators; } @@ -368,24 +374,33 @@ export class EditContentForm extends Form { this.findArrayItemForm(field, language).removeAt(index); } - public insertArrayItem(field: RootFieldDto, language: AppLanguageDto) { + public insertArrayItem(field: RootFieldDto, language: AppLanguageDto, source?: FormGroup) { if (field.nested.length > 0) { const formControl = this.findArrayItemForm(field, language); - this.addArrayItem(field, language, formControl); + this.addArrayItem(field, language, formControl, source); } } - private addArrayItem(field: RootFieldDto, language: AppLanguageDto | null, partitionForm: FormArray) { + private addArrayItem(field: RootFieldDto, language: AppLanguageDto | null, partitionForm: FormArray, source?: FormGroup) { const itemForm = new FormGroup({}); let isOptional = field.isLocalizable && !!language && language.isOptional; for (let nested of field.nested) { const nestedValidators = FieldValidatorsFactory.createValidators(nested, isOptional); - const nestedDefault = FieldDefaultValue.get(nested); - itemForm.setControl(nested.name, new FormControl(nestedDefault, nestedValidators)); + let value = FieldDefaultValue.get(nested); + + if (source) { + const sourceField = source.get(nested.name); + + if (sourceField) { + value = sourceField.value; + } + } + + itemForm.setControl(nested.name, new FormControl(value, nestedValidators)); } partitionForm.push(itemForm); diff --git a/src/Squidex/app/theme/icomoon/demo-files/demo.css b/src/Squidex/app/theme/icomoon/demo-files/demo.css index 3ecabc189..1d2e3901d 100644 --- a/src/Squidex/app/theme/icomoon/demo-files/demo.css +++ b/src/Squidex/app/theme/icomoon/demo-files/demo.css @@ -147,16 +147,16 @@ p { font-size: 16px; } .fs1 { - font-size: 24px; + font-size: 28px; } .fs2 { - font-size: 32px; + font-size: 24px; } .fs3 { - font-size: 20px; + font-size: 24px; } .fs4 { - font-size: 32px; + font-size: 20px; } .fs5 { font-size: 32px; diff --git a/src/Squidex/app/theme/icomoon/demo.html b/src/Squidex/app/theme/icomoon/demo.html index ddaa0218e..bf9c46c99 100644 --- a/src/Squidex/app/theme/icomoon/demo.html +++ b/src/Squidex/app/theme/icomoon/demo.html @@ -9,20 +9,20 @@
-

Font Name: icomoon (Glyphs: 101)

+

Font Name: icomoon (Glyphs: 112)

-

Grid Size: 24

+

Grid Size: 14

- + - icon-drag2 + icon-clone
- - + +
liga: @@ -31,14 +31,14 @@
- + - icon-comments + icon-control-Tags
- - + +
liga: @@ -47,14 +47,14 @@
- + - icon-backup + icon-control-Checkboxes
- - + +
liga: @@ -63,14 +63,14 @@
- + - icon-support + icon-control-Html
- - + +
liga: @@ -79,14 +79,14 @@
- + - icon-control-RichText + icon-single-content
- - + +
liga: @@ -95,1018 +95,1018 @@
- + - icon-download + icon-multiple-content
- - + +
liga:
-
-
-

Grid Size: 16

-
+
- + - icon-spinner2 + icon-type-Array
- - + +
liga:
-
+
- + - icon-star-full + icon-exclamation
- - + +
liga:
-
+
- + - icon-star-empty + icon-orleans
- - + +
liga:
-
+
- + - icon-twitter + icon-document-lock
- - + +
liga:
-
+
- + - icon-hour-glass + icon-document-unpublish
- - + +
liga:
-
+
- + - icon-spinner + icon-angle-down
- - + +
liga:
-
+
- + - icon-clock + icon-angle-left
- - + +
liga:
-
+
- + - icon-bin2 + icon-angle-right
- - + +
liga:
-
+
- + - icon-earth + icon-angle-up
- - + +
liga: - +
-
+
- + - icon-elapsed + icon-api
- - + +
liga:
-
+
- + - icon-google + icon-assets
- - + +
liga:
-
+
- + - icon-lock + icon-bug
- - + +
liga:
-
+
- + - icon-microsoft + icon-caret-down
- - + +
liga:
-
+
- + - icon-pause + icon-caret-left
- - + +
liga:
-
+
- + - icon-play + icon-caret-right
- - + +
liga:
-
+
- + - icon-reset + icon-caret-up
- - + +
liga:
-
+
- + - icon-settings2 + icon-contents
- - + +
liga:
-
+
- + - icon-timeout + icon-trigger-ContentChanged
- - + +
liga:
-
+
- + - icon-unlocked + icon-control-Date
- - + +
liga:
-
-
-

Grid Size: 20

-
+
- + - icon-grid1 + icon-control-DateTime
- - + +
liga:
-
+
- + - icon-list + icon-control-Markdown
- - + +
liga:
-
+
- + - icon-info + icon-grid
- - + +
liga:
-
-
-

Grid Size: 32

-
+
- + - icon-control-Color + icon-list1
- - + +
liga:
-
+
- + - icon-browser + icon-user-o
- - + +
liga:
-
+
- + - icon-checkmark + icon-rules
- - + +
liga:
-
+
+
+

Grid Size: 24

+
- + - icon-control-Stars + icon-minus-square
- - + +
liga:
-
-
-

Grid Size: Unknown

-
+
- + - icon-prerender + icon-plus-square
- - + +
liga:
-
+
- + - icon-circle + icon-drag2
- - + +
liga:
-
+
- + - icon-control-Slug + icon-comments
- - + +
liga:
-
+
- + - icon-type-Tags + icon-backup
- - + +
liga:
-
+
- + - icon-activity + icon-support
- - + +
liga:
-
+
- + - icon-history + icon-control-RichText
- - + +
liga:
-
+
- + - icon-time + icon-download
- - + +
liga:
-
+
+
+

Grid Size: 16

+
- + - icon-add + icon-caret-bottom
- - + +
liga:
-
+
- + - icon-plus + icon-caret-top
- - + +
liga:
-
+
- + - icon-check-circle + icon-show
- - + +
liga:
-
+
- + - icon-check-circle-filled + icon-show-all
- - + +
liga:
-
+
- + - icon-close + icon-hide
- - + +
liga:
-
+
- + - icon-type-References + icon-hide-all
- - + +
liga:
-
+
- + - icon-control-Checkbox + icon-spinner2
- - + +
liga:
-
+
- + - icon-control-Dropdown + icon-star-full
- - + +
liga:
-
+
- + - icon-control-Input + icon-star-empty
- - + +
liga:
-
+
- + - icon-control-Radio + icon-twitter
- - + +
liga:
-
+
- + - icon-control-TextArea + icon-hour-glass
- - + +
liga:
-
+
- + - icon-control-Toggle + icon-spinner
- - + +
liga:
-
+
- + - icon-copy + icon-clock
- - + +
liga:
-
+
- + - icon-dashboard + icon-bin2
- - + +
liga:
-
+
- + - icon-delete + icon-earth
- - + +
liga: - +
-
+
- + - icon-bin + icon-elapsed
- - + +
liga:
-
+
- + - icon-delete-filled + icon-google
- - + +
liga:
-
+
- + - icon-document-delete + icon-lock
- - + +
liga:
-
+
- + - icon-document-disable + icon-microsoft
- - + +
liga:
-
+
- + - icon-document-publish + icon-pause
- - + +
liga:
-
+
- + - icon-drag + icon-play
- - + +
liga:
-
+
- + - icon-filter + icon-reset
- - + +
liga:
-
+
- + - icon-github + icon-settings2
- - + +
liga:
-
+
- + - icon-help + icon-timeout
- - + +
liga:
-
+
- + - icon-location + icon-unlocked
- - + +
liga:
-
+
+
+

Grid Size: 20

+
- + - icon-control-Map + icon-grid1
- - + +
liga:
-
+
- + - icon-type-Geolocation + icon-list
- - + +
liga:
-
+
-
- - + +
liga:
+
+
+

Grid Size: 32

- + - icon-media + icon-control-Color
- - + +
liga: @@ -1115,14 +1115,14 @@
- + - icon-type-Assets + icon-browser
- - + +
liga: @@ -1131,14 +1131,14 @@
- + - icon-trigger-AssetChanged + icon-checkmark
- - + +
liga: @@ -1147,241 +1147,417 @@
- + - icon-more + icon-control-Stars
- - + +
liga:
-
+
+
+

Grid Size: Unknown

+
- + - icon-dots + icon-prerender
- - + +
liga:
-
+
- + - icon-pencil + icon-circle
- - + +
liga:
-
+
- + - icon-reference + icon-control-Slug
- - + +
liga:
-
+
- + - icon-schemas + icon-type-Tags
- - + +
liga:
-
+
- + - icon-search + icon-activity
- - + +
liga:
-
+
- + - icon-settings + icon-history
- - + +
liga:
-
+
- + - icon-type-Boolean + icon-time
- - + +
liga:
-
+
- + - icon-type-DateTime + icon-add
- - + +
liga:
-
+
- + - icon-type-Json + icon-plus
- - + +
liga:
-
+
- + - icon-json + icon-check-circle
- - + +
liga:
-
+
+
+ + + + icon-check-circle-filled +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-close +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-type-References +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-control-Checkbox +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-control-Dropdown +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-control-Input +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-control-Radio +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-control-TextArea +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-control-Toggle +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-copy +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-dashboard +
+
+ + +
+
+ liga: + +
+
+
- + - icon-type-Number + icon-delete
- - + +
liga:
-
+
- + - icon-type-String + icon-bin
- - + +
liga:
-
+
- + - icon-user + icon-delete-filled
- - + +
liga:
-
-
-

Grid Size: 14

- + - icon-control-Html + icon-document-delete
- - + +
liga: @@ -1390,14 +1566,14 @@
- + - icon-single-content + icon-document-disable
- - + +
liga: @@ -1406,14 +1582,14 @@
- + - icon-multiple-content + icon-document-publish
- - + +
liga: @@ -1422,14 +1598,14 @@
- + - icon-type-Array + icon-drag
- - + +
liga: @@ -1438,14 +1614,14 @@
- + - icon-exclamation + icon-filter
- - + +
liga: @@ -1454,14 +1630,14 @@
- + - icon-orleans + icon-github
- - + +
liga: @@ -1470,14 +1646,14 @@
- + - icon-document-lock + icon-help
- - + +
liga: @@ -1486,14 +1662,14 @@
- + - icon-document-unpublish + icon-location
- - + +
liga: @@ -1502,14 +1678,14 @@
- + - icon-angle-down + icon-control-Map
- - + +
liga: @@ -1518,14 +1694,14 @@
- + - icon-angle-left + icon-type-Geolocation
- - + +
liga: @@ -1534,14 +1710,14 @@
- + - icon-angle-right + icon-logo
- - + +
liga: @@ -1550,14 +1726,14 @@
- + - icon-angle-up + icon-media
- - + +
liga: @@ -1566,14 +1742,14 @@
- + - icon-api + icon-type-Assets
- - + +
liga: @@ -1582,14 +1758,14 @@
- + - icon-assets + icon-trigger-AssetChanged
- - + +
liga: @@ -1598,14 +1774,14 @@
- + - icon-bug + icon-more
- - + +
liga: @@ -1614,14 +1790,14 @@
- + - icon-caret-down + icon-dots
- - + +
liga: @@ -1630,14 +1806,14 @@
- + - icon-caret-left + icon-pencil
- - + +
liga: @@ -1646,14 +1822,14 @@
- + - icon-caret-right + icon-reference
- - + +
liga: @@ -1662,14 +1838,14 @@
- + - icon-caret-up + icon-schemas
- - + +
liga: @@ -1678,14 +1854,14 @@
- + - icon-contents + icon-search
- - + +
liga: @@ -1694,14 +1870,14 @@
- + - icon-trigger-ContentChanged + icon-settings
- - + +
liga: @@ -1710,14 +1886,14 @@
- + - icon-control-Date + icon-type-Boolean
- - + +
liga: @@ -1726,14 +1902,14 @@
- + - icon-control-DateTime + icon-type-DateTime
- - + +
liga: @@ -1742,14 +1918,14 @@
- + - icon-control-Markdown + icon-type-Json
- - + +
liga: @@ -1758,14 +1934,14 @@
- + - icon-grid + icon-json
- - + +
liga: @@ -1774,14 +1950,14 @@
- + - icon-list1 + icon-type-Number
- - + +
liga: @@ -1790,14 +1966,14 @@
- + - icon-user-o + icon-type-String
- - + +
liga: @@ -1806,14 +1982,14 @@
- + - icon-rules + icon-user
- - + +
liga: diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.eot b/src/Squidex/app/theme/icomoon/fonts/icomoon.eot index 9b248387b..3e1296635 100644 Binary files a/src/Squidex/app/theme/icomoon/fonts/icomoon.eot and b/src/Squidex/app/theme/icomoon/fonts/icomoon.eot differ diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg index 8cb1b5748..a43377165 100644 --- a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg +++ b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg @@ -105,6 +105,17 @@ + + + + + + + + + + + diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf b/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf index 05f1693ef..72ac12c4b 100644 Binary files a/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf and b/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf differ diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.woff b/src/Squidex/app/theme/icomoon/fonts/icomoon.woff index 7c7f5cdbe..94443d707 100644 Binary files a/src/Squidex/app/theme/icomoon/fonts/icomoon.woff and b/src/Squidex/app/theme/icomoon/fonts/icomoon.woff differ diff --git a/src/Squidex/app/theme/icomoon/icons/caret-bottom.svg b/src/Squidex/app/theme/icomoon/icons/caret-bottom.svg new file mode 100644 index 000000000..55248276b --- /dev/null +++ b/src/Squidex/app/theme/icomoon/icons/caret-bottom.svg @@ -0,0 +1,66 @@ + + + + + + + + image/svg+xml + + + + + + + caret-down + + + + + diff --git a/src/Squidex/app/theme/icomoon/icons/caret-top.svg b/src/Squidex/app/theme/icomoon/icons/caret-top.svg new file mode 100644 index 000000000..714f0cc5b --- /dev/null +++ b/src/Squidex/app/theme/icomoon/icons/caret-top.svg @@ -0,0 +1,66 @@ + + + + + + + + image/svg+xml + + + + + + + caret-up + + + + + diff --git a/src/Squidex/app/theme/icomoon/icons/control-Checkboxes.svg b/src/Squidex/app/theme/icomoon/icons/control-Checkboxes.svg new file mode 100644 index 000000000..a490a8d81 --- /dev/null +++ b/src/Squidex/app/theme/icomoon/icons/control-Checkboxes.svg @@ -0,0 +1,68 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/src/Squidex/app/theme/icomoon/icons/control-Tags.svg b/src/Squidex/app/theme/icomoon/icons/control-Tags.svg new file mode 100644 index 000000000..b77cc0f7d --- /dev/null +++ b/src/Squidex/app/theme/icomoon/icons/control-Tags.svg @@ -0,0 +1,75 @@ + + + +image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/src/Squidex/app/theme/icomoon/icons/hide-all.svg b/src/Squidex/app/theme/icomoon/icons/hide-all.svg new file mode 100644 index 000000000..69ed451c1 --- /dev/null +++ b/src/Squidex/app/theme/icomoon/icons/hide-all.svg @@ -0,0 +1,70 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/src/Squidex/app/theme/icomoon/icons/hide.svg b/src/Squidex/app/theme/icomoon/icons/hide.svg new file mode 100644 index 000000000..e86579ac2 --- /dev/null +++ b/src/Squidex/app/theme/icomoon/icons/hide.svg @@ -0,0 +1,62 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/src/Squidex/app/theme/icomoon/icons/show-all.svg b/src/Squidex/app/theme/icomoon/icons/show-all.svg new file mode 100644 index 000000000..afd55db27 --- /dev/null +++ b/src/Squidex/app/theme/icomoon/icons/show-all.svg @@ -0,0 +1,70 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/src/Squidex/app/theme/icomoon/icons/show.svg b/src/Squidex/app/theme/icomoon/icons/show.svg new file mode 100644 index 000000000..eb6d4a386 --- /dev/null +++ b/src/Squidex/app/theme/icomoon/icons/show.svg @@ -0,0 +1,62 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/src/Squidex/app/theme/icomoon/selection.json b/src/Squidex/app/theme/icomoon/selection.json index d4997ec93..c419be8cd 100644 --- a/src/Squidex/app/theme/icomoon/selection.json +++ b/src/Squidex/app/theme/icomoon/selection.json @@ -1 +1 @@ -{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M170 640v-86h684v86h-684zM854 384v86h-684v-86h684z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["drag_handle"],"grid":24},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":24,"code":59745,"name":"drag2"},"setIdx":0,"setId":6,"iconIdx":0},{"icon":{"paths":["M854 682v-512h-684v598l86-86h598zM854 86c46 0 84 38 84 84v512c0 46-38 86-84 86h-598l-170 170v-768c0-46 38-84 84-84h684z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["chat_bubble_outline"],"grid":24},"attrs":[{}],"properties":{"order":1,"id":0,"name":"comments","prevSize":24,"code":59743},"setIdx":0,"setId":6,"iconIdx":1},{"icon":{"paths":["M512 128c212 0 384 172 384 384s-172 384-384 384c-88 0-170-30-234-80l60-60c50 34 110 54 174 54 166 0 298-132 298-298s-132-298-298-298-298 132-298 298h128l-172 170-170-170h128c0-212 172-384 384-384zM598 512c0 46-40 86-86 86s-86-40-86-86 40-86 86-86 86 40 86 86z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["settings_backup_restore"],"grid":24},"attrs":[{}],"properties":{"order":1,"id":2,"prevSize":24,"code":59739,"name":"backup"},"setIdx":5,"setId":1,"iconIdx":21},{"icon":{"paths":["M726 512c0 24-20 42-44 42h-426l-170 172v-598c0-24 18-42 42-42h554c24 0 44 18 44 42v384zM896 256c24 0 42 18 42 42v640l-170-170h-470c-24 0-42-18-42-42v-86h554v-384h86z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["question_answer"],"grid":24},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":24,"code":59738,"name":"support"},"setIdx":5,"setId":1,"iconIdx":22},{"icon":{"paths":["M918 384v128h-128v298h-128v-298h-128v-128h384zM106 170h556v128h-214v512h-128v-512h-214v-128z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["text_fields"],"grid":24},"attrs":[{}],"properties":{"order":75,"id":0,"prevSize":24,"code":59705,"name":"control-RichText"},"setIdx":5,"setId":1,"iconIdx":23},{"icon":{"paths":["M640 85.333q78 0 149.167 30.5t122.5 81.833 81.833 122.5 30.5 149.167q0 85-35 160.667t-96.667 129.167-140 77.5l21-20.667q18-18.333 28-42.667 9.333-22.667 9.333-49.333 0-6.667-0.333-9.333 59.333-41.333 93.833-105.833t34.5-139.5q0-60.667-23.667-116t-63.667-95.333-95.333-63.667-116-23.667q-55.333 0-106.5 19.833t-90 53.833-65 81.333-33.833 101h-88.667q-70.667 0-120.667 50t-50 120.667q0 38.667 15.167 71.667t39.833 54.167 54.833 33 60.833 11.833h50q11.667 29.333 30 48l37.667 37.333h-117.667q-69.667 0-128.5-34.333t-93.167-93.167-34.333-128.5 34.333-128.5 93.167-93.167 128.5-34.333h22q26.333-74.333 79.333-132.167t126.833-90.833 155.833-33zM554.667 426.667q17.667 0 30.167 12.5t12.5 30.167v281l55-55.333q12.333-12.333 30.333-12.333 18.333 0 30.5 12.167t12.167 30.5q0 18-12.333 30.333l-128 128q-12.333 12.333-30.333 12.333t-30.333-12.333l-128-128q-12.333-13-12.333-30.333 0-17.667 12.5-30.167t30.167-12.5q18 0 30.333 12.333l55 55.333v-281q0-17.667 12.5-30.167t30.167-12.5z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["cloud-download"],"grid":24},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":24,"code":59710,"name":"download"},"setIdx":5,"setId":1,"iconIdx":24},{"icon":{"paths":["M512 1024c-136.76 0-265.334-53.258-362.040-149.96-96.702-96.706-149.96-225.28-149.96-362.040 0-96.838 27.182-191.134 78.606-272.692 50-79.296 120.664-143.372 204.356-185.3l43 85.832c-68.038 34.084-125.492 86.186-166.15 150.67-41.746 66.208-63.812 142.798-63.812 221.49 0 229.382 186.618 416 416 416s416-186.618 416-416c0-78.692-22.066-155.282-63.81-221.49-40.66-64.484-98.114-116.584-166.15-150.67l43-85.832c83.692 41.928 154.358 106.004 204.356 185.3 51.422 81.558 78.604 175.854 78.604 272.692 0 136.76-53.258 265.334-149.96 362.040-96.706 96.702-225.28 149.96-362.040 149.96z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["spinner","loading","loading-wheel","busy","wait"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59737,"name":"spinner2"},"setIdx":1,"setId":5,"iconIdx":0},{"icon":{"paths":["M1024 397.050l-353.78-51.408-158.22-320.582-158.216 320.582-353.784 51.408 256 249.538-60.432 352.352 316.432-166.358 316.432 166.358-60.434-352.352 256.002-249.538z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["star-full","rate","star","favorite","bookmark"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":2,"prevSize":32,"code":59741,"name":"star-full"},"setIdx":5,"setId":1,"iconIdx":3},{"icon":{"paths":["M1024 397.050l-353.78-51.408-158.22-320.582-158.216 320.582-353.784 51.408 256 249.538-60.432 352.352 316.432-166.358 316.432 166.358-60.434-352.352 256.002-249.538zM512 753.498l-223.462 117.48 42.676-248.83-180.786-176.222 249.84-36.304 111.732-226.396 111.736 226.396 249.836 36.304-180.788 176.222 42.678 248.83-223.462-117.48z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["star-empty","rate","star","favorite","bookmark"],"grid":16},"attrs":[{}],"properties":{"order":2,"id":1,"prevSize":32,"code":59742,"name":"star-empty"},"setIdx":5,"setId":1,"iconIdx":4},{"icon":{"paths":["M1024 226.4c-37.6 16.8-78.2 28-120.6 33 43.4-26 76.6-67.2 92.4-116.2-40.6 24-85.6 41.6-133.4 51-38.4-40.8-93-66.2-153.4-66.2-116 0-210 94-210 210 0 16.4 1.8 32.4 5.4 47.8-174.6-8.8-329.4-92.4-433-219.6-18 31-28.4 67.2-28.4 105.6 0 72.8 37 137.2 93.4 174.8-34.4-1-66.8-10.6-95.2-26.2 0 0.8 0 1.8 0 2.6 0 101.8 72.4 186.8 168.6 206-17.6 4.8-36.2 7.4-55.4 7.4-13.6 0-26.6-1.4-39.6-3.8 26.8 83.4 104.4 144.2 196.2 146-72 56.4-162.4 90-261 90-17 0-33.6-1-50.2-3 93.2 59.8 203.6 94.4 322.2 94.4 386.4 0 597.8-320.2 597.8-597.8 0-9.2-0.2-18.2-0.6-27.2 41-29.4 76.6-66.4 104.8-108.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["twitter","brand","tweet","social"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59740,"name":"twitter"},"setIdx":5,"setId":1,"iconIdx":5},{"icon":{"paths":["M728.992 512c137.754-87.334 231.008-255.208 231.008-448 0-21.676-1.192-43.034-3.478-64h-889.042c-2.29 20.968-3.48 42.326-3.48 64 0 192.792 93.254 360.666 231.006 448-137.752 87.334-231.006 255.208-231.006 448 0 21.676 1.19 43.034 3.478 64h889.042c2.288-20.966 3.478-42.324 3.478-64 0.002-192.792-93.252-360.666-231.006-448zM160 960c0-186.912 80.162-345.414 224-397.708v-100.586c-143.838-52.29-224-210.792-224-397.706v0h704c0 186.914-80.162 345.416-224 397.706v100.586c143.838 52.294 224 210.796 224 397.708h-704zM619.626 669.594c-71.654-40.644-75.608-93.368-75.626-125.366v-64.228c0-31.994 3.804-84.914 75.744-125.664 38.504-22.364 71.808-56.348 97.048-98.336h-409.582c25.266 42.032 58.612 76.042 97.166 98.406 71.654 40.644 75.606 93.366 75.626 125.366v64.228c0 31.992-3.804 84.914-75.744 125.664-72.622 42.18-126.738 125.684-143.090 226.336h501.67c-16.364-100.708-70.53-184.248-143.212-226.406z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["hour-glass","loading","busy","wait"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":32,"code":59732,"name":"hour-glass"},"setIdx":5,"setId":1,"iconIdx":6},{"icon":{"paths":["M192 512c0-12.18 0.704-24.196 2.030-36.022l-184.98-60.104c-5.916 31.14-9.050 63.264-9.050 96.126 0 147.23 62.166 279.922 161.654 373.324l114.284-157.296c-52.124-56.926-83.938-132.758-83.938-216.028zM832 512c0 83.268-31.812 159.102-83.938 216.028l114.284 157.296c99.488-93.402 161.654-226.094 161.654-373.324 0-32.862-3.132-64.986-9.048-96.126l-184.98 60.104c1.324 11.828 2.028 23.842 2.028 36.022zM576 198.408c91.934 18.662 169.544 76.742 214.45 155.826l184.978-60.102c-73.196-155.42-222.24-268.060-399.428-290.156v194.432zM233.55 354.232c44.906-79.084 122.516-137.164 214.45-155.826v-194.43c-177.188 22.096-326.23 134.736-399.426 290.154l184.976 60.102zM644.556 803.328c-40.39 18.408-85.272 28.672-132.556 28.672s-92.166-10.264-132.554-28.67l-114.292 157.31c73.206 40.366 157.336 63.36 246.846 63.36s173.64-22.994 246.848-63.36l-114.292-157.312z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["spinner","loading","loading-wheel","busy","wait"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59731,"name":"spinner"},"setIdx":5,"setId":1,"iconIdx":7},{"icon":{"paths":["M658.744 749.256l-210.744-210.746v-282.51h128v229.49l173.256 173.254zM512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 896c-212.078 0-384-171.922-384-384s171.922-384 384-384c212.078 0 384 171.922 384 384s-171.922 384-384 384z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["clock","time","schedule"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59728,"name":"clock"},"setIdx":5,"setId":1,"iconIdx":8},{"icon":{"paths":["M128 320v640c0 35.2 28.8 64 64 64h576c35.2 0 64-28.8 64-64v-640h-704zM320 896h-64v-448h64v448zM448 896h-64v-448h64v448zM576 896h-64v-448h64v448zM704 896h-64v-448h64v448z","M848 128h-208v-80c0-26.4-21.6-48-48-48h-224c-26.4 0-48 21.6-48 48v80h-208c-26.4 0-48 21.6-48 48v80h832v-80c0-26.4-21.6-48-48-48zM576 128h-192v-63.198h192v63.198z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"tags":["bin","trashcan","remove","delete","recycle","dispose"],"grid":16},"attrs":[{},{}],"properties":{"order":1,"id":0,"name":"bin2","prevSize":32,"code":59650},"setIdx":5,"setId":1,"iconIdx":9},{"icon":{"paths":["M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 960.002c-62.958 0-122.872-13.012-177.23-36.452l233.148-262.29c5.206-5.858 8.082-13.422 8.082-21.26v-96c0-17.674-14.326-32-32-32-112.99 0-232.204-117.462-233.374-118.626-6-6.002-14.14-9.374-22.626-9.374h-128c-17.672 0-32 14.328-32 32v192c0 12.122 6.848 23.202 17.69 28.622l110.31 55.156v187.886c-116.052-80.956-192-215.432-192-367.664 0-68.714 15.49-133.806 43.138-192h116.862c8.488 0 16.626-3.372 22.628-9.372l128-128c6-6.002 9.372-14.14 9.372-22.628v-77.412c40.562-12.074 83.518-18.588 128-18.588 70.406 0 137.004 16.26 196.282 45.2-4.144 3.502-8.176 7.164-12.046 11.036-36.266 36.264-56.236 84.478-56.236 135.764s19.97 99.5 56.236 135.764c36.434 36.432 85.218 56.264 135.634 56.26 3.166 0 6.342-0.080 9.518-0.236 13.814 51.802 38.752 186.656-8.404 372.334-0.444 1.744-0.696 3.488-0.842 5.224-81.324 83.080-194.7 134.656-320.142 134.656z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["earth","globe","language","web","internet","sphere","planet"],"defaultCode":59850,"grid":16},"attrs":[],"properties":{"ligatures":"earth, globe2","name":"earth","id":202,"order":91,"prevSize":32,"code":59850},"setIdx":5,"setId":1,"iconIdx":10},{"icon":{"paths":["M512.002 193.212v-65.212h128v-64c0-35.346-28.654-64-64.002-64h-191.998c-35.346 0-64 28.654-64 64v64h128v65.212c-214.798 16.338-384 195.802-384 414.788 0 229.75 186.25 416 416 416s416-186.25 416-416c0-218.984-169.202-398.448-384-414.788zM706.276 834.274c-60.442 60.44-140.798 93.726-226.274 93.726s-165.834-33.286-226.274-93.726c-60.44-60.44-93.726-140.8-93.726-226.274s33.286-165.834 93.726-226.274c58.040-58.038 134.448-91.018 216.114-93.548l-21.678 314.020c-1.86 26.29 12.464 37.802 31.836 37.802s33.698-11.512 31.836-37.802l-21.676-314.022c81.666 2.532 158.076 35.512 216.116 93.55 60.44 60.44 93.726 140.8 93.726 226.274s-33.286 165.834-93.726 226.274z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["stopwatch","time","speed","meter","chronometer"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":2,"prevSize":32,"code":59715,"name":"elapsed"},"setIdx":5,"setId":1,"iconIdx":11},{"icon":{"paths":["M522.2 438.8v175.6h290.4c-11.8 75.4-87.8 220.8-290.4 220.8-174.8 0-317.4-144.8-317.4-323.2s142.6-323.2 317.4-323.2c99.4 0 166 42.4 204 79l139-133.8c-89.2-83.6-204.8-134-343-134-283 0-512 229-512 512s229 512 512 512c295.4 0 491.6-207.8 491.6-500.2 0-33.6-3.6-59.2-8-84.8l-483.6-0.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["google","brand"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59707,"name":"google"},"setIdx":5,"setId":1,"iconIdx":12},{"icon":{"paths":["M592 448h-16v-192c0-105.87-86.13-192-192-192h-128c-105.87 0-192 86.13-192 192v192h-16c-26.4 0-48 21.6-48 48v480c0 26.4 21.6 48 48 48h544c26.4 0 48-21.6 48-48v-480c0-26.4-21.6-48-48-48zM192 256c0-35.29 28.71-64 64-64h128c35.29 0 64 28.71 64 64v192h-256v-192z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["lock","secure","private","encrypted"],"grid":16},"attrs":[{}],"properties":{"order":2,"id":0,"prevSize":32,"code":59700,"name":"lock"},"setIdx":5,"setId":1,"iconIdx":13},{"icon":{"paths":["M0.35 512l-0.35-312.074 384-52.144v364.218zM448 138.482l511.872-74.482v448h-511.872zM959.998 576l-0.126 448-511.872-72.016v-375.984zM384 943.836l-383.688-52.594-0.020-315.242h383.708z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["windows8","brand","os"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":32,"code":59712,"name":"microsoft"},"setIdx":5,"setId":1,"iconIdx":14},{"icon":{"paths":["M128 128h320v768h-320zM576 128h320v768h-320z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["pause","player"],"grid":16},"attrs":[{}],"properties":{"order":2,"id":1,"prevSize":32,"code":59695,"name":"pause"},"setIdx":5,"setId":1,"iconIdx":15},{"icon":{"paths":["M192 128l640 384-640 384z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["play","player"],"grid":16},"attrs":[{}],"properties":{"order":3,"id":0,"prevSize":32,"code":59696,"name":"play"},"setIdx":5,"setId":1,"iconIdx":16},{"icon":{"paths":["M889.68 166.32c-93.608-102.216-228.154-166.32-377.68-166.32-282.77 0-512 229.23-512 512h96c0-229.75 186.25-416 416-416 123.020 0 233.542 53.418 309.696 138.306l-149.696 149.694h352v-352l-134.32 134.32z","M928 512c0 229.75-186.25 416-416 416-123.020 0-233.542-53.418-309.694-138.306l149.694-149.694h-352v352l134.32-134.32c93.608 102.216 228.154 166.32 377.68 166.32 282.77 0 512-229.23 512-512h-96z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"tags":["loop","repeat","player","reload","refresh","update","synchronize","arrows"],"grid":16},"attrs":[{},{}],"properties":{"order":49,"id":2,"prevSize":32,"code":59694,"name":"reset"},"setIdx":5,"setId":1,"iconIdx":17},{"icon":{"paths":["M933.79 610.25c-53.726-93.054-21.416-212.304 72.152-266.488l-100.626-174.292c-28.75 16.854-62.176 26.518-97.846 26.518-107.536 0-194.708-87.746-194.708-195.99h-201.258c0.266 33.41-8.074 67.282-25.958 98.252-53.724 93.056-173.156 124.702-266.862 70.758l-100.624 174.292c28.97 16.472 54.050 40.588 71.886 71.478 53.638 92.908 21.512 211.92-71.708 266.224l100.626 174.292c28.65-16.696 61.916-26.254 97.4-26.254 107.196 0 194.144 87.192 194.7 194.958h201.254c-0.086-33.074 8.272-66.57 25.966-97.218 53.636-92.906 172.776-124.594 266.414-71.012l100.626-174.29c-28.78-16.466-53.692-40.498-71.434-71.228zM512 719.332c-114.508 0-207.336-92.824-207.336-207.334 0-114.508 92.826-207.334 207.336-207.334 114.508 0 207.332 92.826 207.332 207.334-0.002 114.51-92.824 207.334-207.332 207.334z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["cog","gear","preferences","settings","generate","control","options"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":32,"code":59693,"name":"settings2"},"setIdx":5,"setId":1,"iconIdx":18},{"icon":{"paths":["M512 128c-247.424 0-448 200.576-448 448s200.576 448 448 448 448-200.576 448-448-200.576-448-448-448zM512 936c-198.824 0-360-161.178-360-360 0-198.824 161.176-360 360-360 198.822 0 360 161.176 360 360 0 198.822-161.178 360-360 360zM934.784 287.174c16.042-28.052 25.216-60.542 25.216-95.174 0-106.040-85.96-192-192-192-61.818 0-116.802 29.222-151.92 74.596 131.884 27.236 245.206 105.198 318.704 212.578v0zM407.92 74.596c-35.116-45.374-90.102-74.596-151.92-74.596-106.040 0-192 85.96-192 192 0 34.632 9.174 67.122 25.216 95.174 73.5-107.38 186.822-185.342 318.704-212.578z","M512 576v-256h-64v320h256v-64z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"tags":["alarm","time","clock"],"grid":16},"attrs":[{},{}],"properties":{"order":2,"id":1,"prevSize":32,"code":59716,"name":"timeout"},"setIdx":5,"setId":1,"iconIdx":19},{"icon":{"paths":["M768 64c105.87 0 192 86.13 192 192v192h-128v-192c0-35.29-28.71-64-64-64h-128c-35.29 0-64 28.71-64 64v192h16c26.4 0 48 21.6 48 48v480c0 26.4-21.6 48-48 48h-544c-26.4 0-48-21.6-48-48v-480c0-26.4 21.6-48 48-48h400v-192c0-105.87 86.13-192 192-192h128z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["unlocked","lock-open"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":32,"code":59699,"name":"unlocked"},"setIdx":5,"setId":1,"iconIdx":20},{"icon":{"paths":["M409.6 204.8h-153.6c-28.314 0-51.2 22.886-51.2 51.2v153.6c0 28.262 22.886 51.2 51.2 51.2h153.6c28.314 0 51.2-22.938 51.2-51.2v-153.6c0-28.262-22.886-51.2-51.2-51.2zM768 204.8h-153.6c-28.314 0-51.2 22.886-51.2 51.2v153.6c0 28.262 22.886 51.2 51.2 51.2h153.6c28.314 0 51.2-22.938 51.2-51.2v-153.6c0-28.262-22.886-51.2-51.2-51.2zM409.6 563.2h-153.6c-28.314 0-51.2 22.886-51.2 51.2v153.6c0 28.262 22.886 51.2 51.2 51.2h153.6c28.314 0 51.2-22.938 51.2-51.2v-153.6c0-28.262-22.886-51.2-51.2-51.2zM768 563.2h-153.6c-28.314 0-51.2 22.886-51.2 51.2v153.6c0 28.262 22.886 51.2 51.2 51.2h153.6c28.314 0 51.2-22.938 51.2-51.2v-153.6c0-28.262-22.886-51.2-51.2-51.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["grid"],"grid":20},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":20,"code":59730,"name":"grid1"},"setIdx":2,"setId":4,"iconIdx":0},{"icon":{"paths":["M737.28 460.8h-296.96c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2h296.96c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2zM839.68 716.8h-399.36c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2h399.36c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2zM440.32 307.2h399.36c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2h-399.36c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2zM276.48 460.8h-92.16c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2h92.16c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2zM276.48 716.8h-92.16c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2h92.16c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2zM276.48 204.8h-92.16c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2h92.16c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["list"],"grid":20},"attrs":[{}],"properties":{"order":1,"id":0,"name":"list","prevSize":20,"code":59726},"setIdx":2,"setId":4,"iconIdx":1},{"icon":{"paths":["M636.518 0c68.608 0 102.912 46.694 102.912 100.198 0 66.816-59.597 128.614-137.165 128.614-64.973 0-102.861-38.4-101.069-101.888 0-53.402 45.107-126.925 135.322-126.925zM425.421 1024c-54.17 0-93.85-33.382-55.962-180.429l62.157-260.71c10.803-41.677 12.595-58.419 0-58.419-16.23 0-86.477 28.774-128.102 57.19l-27.034-45.056c131.686-111.923 283.187-177.51 348.211-177.51 54.118 0 63.13 65.178 36.096 165.376l-71.219 274.022c-12.595 48.384-7.219 65.075 5.427 65.075 16.23 0 69.478-20.070 121.805-61.798l30.72 41.677c-128.102 130.406-268.032 180.582-322.099 180.582z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["info"],"grid":20},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":20,"code":59708,"name":"info"},"setIdx":5,"setId":1,"iconIdx":100},{"icon":{"paths":["M832 416h-320v64h-64v-96h384v-192h-32v96c0 17.664-14.336 32-32 32h-576c-17.696 0-32-14.336-32-32v-128c0-17.696 14.304-32 32-32h576c17.664 0 32 14.304 32 32h64v256h-32zM736 160h-512v32h512v-32zM544 832c0 35.328-28.672 64-64 64s-64-28.672-64-64v-320h128v320zM480 786.656c-17.696 0-32 14.336-32 32 0 17.696 14.304 32 32 32 17.664 0 32-14.304 32-32 0-17.664-14.336-32-32-32z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["paint","tool"],"grid":32},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59725,"name":"control-Color"},"setIdx":3,"setId":3,"iconIdx":0},{"icon":{"paths":["M1328 320c-8.832 0-16 7.168-16 16v640c0 8.832-7.168 16-16 16h-1248c-8.832 0-16-7.168-16-16v-640c0-8.832-7.168-16-16-16s-16 7.168-16 16v640c0 26.464 21.536 48 48 48h1248c26.464 0 48-21.536 48-48v-640c0-8.832-7.168-16-16-16zM1296 0h-1248c-26.464 0-48 21.536-48 48v192c0 8.832 7.168 16 16 16h1312c8.832 0 16-7.168 16-16v-192c0-26.464-21.536-48-48-48zM1312 224h-1280v-176c0-8.832 7.168-16 16-16h1248c8.832 0 16 7.168 16 16v176zM560 896c8.832 0 16-7.168 16-16v-512c0-8.832-7.168-16-16-16h-416c-8.832 0-16 7.168-16 16v512c0 8.832 7.168 16 16 16h416zM160 384h384v480h-384v-480zM720 480h480c8.832 0 16-7.168 16-16s-7.168-16-16-16h-480c-8.832 0-16 7.168-16 16s7.168 16 16 16zM720 640h480c8.832 0 16-7.168 16-16s-7.168-16-16-16h-480c-8.832 0-16 7.168-16 16s7.168 16 16 16zM720 800h480c8.832 0 16-7.168 16-16s-7.168-16-16-16h-480c-8.832 0-16 7.168-16 16s7.168 16 16 16zM96 128c0 17.673 14.327 32 32 32s32-14.327 32-32c0-17.673-14.327-32-32-32s-32 14.327-32 32zM224 128c0 17.673 14.327 32 32 32s32-14.327 32-32c0-17.673-14.327-32-32-32s-32 14.327-32 32zM352 128c0 17.673 14.327 32 32 32s32-14.327 32-32c0-17.673-14.327-32-32-32s-32 14.327-32 32z"],"attrs":[{}],"width":1344,"isMulticolor":false,"isMulticolor2":false,"tags":["browser","window","software","program"],"grid":32},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59701,"name":"browser"},"setIdx":5,"setId":1,"iconIdx":97},{"icon":{"paths":["M927.936 272.992l-68.288-68.288c-12.608-12.576-32.96-12.576-45.536 0l-409.44 409.44-194.752-196.16c-12.576-12.576-32.928-12.576-45.536 0l-68.288 68.288c-12.576 12.608-12.576 32.96 0 45.536l285.568 287.488c12.576 12.576 32.96 12.576 45.536 0l500.736-500.768c12.576-12.544 12.576-32.96 0-45.536z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["checkmark","tick","approve","submit"],"grid":32},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59714,"name":"checkmark"},"setIdx":5,"setId":1,"iconIdx":98},{"icon":{"paths":["M1020.192 401.824c-8.864-25.568-31.616-44.288-59.008-48.352l-266.432-39.616-115.808-240.448c-12.192-25.248-38.272-41.408-66.944-41.408s-54.752 16.16-66.944 41.408l-115.808 240.448-266.464 39.616c-27.36 4.064-50.112 22.784-58.944 48.352-8.8 25.632-2.144 53.856 17.184 73.12l195.264 194.944-45.28 270.432c-4.608 27.232 7.2 54.56 30.336 70.496 12.704 8.736 27.648 13.184 42.592 13.184 12.288 0 24.608-3.008 35.776-8.992l232.288-125.056 232.32 125.056c11.168 5.984 23.488 8.992 35.744 8.992 14.944 0 29.888-4.448 42.624-13.184 23.136-15.936 34.88-43.264 30.304-70.496l-45.312-270.432 195.328-194.944c19.296-19.296 25.92-47.52 17.184-73.12zM754.816 619.616c-16.384 16.32-23.808 39.328-20.064 61.888l45.312 270.432-232.32-124.992c-11.136-6.016-23.424-8.992-35.776-8.992-12.288 0-24.608 3.008-35.744 8.992l-232.32 124.992 45.312-270.432c3.776-22.56-3.648-45.568-20.032-61.888l-195.264-194.944 266.432-39.68c24.352-3.616 45.312-18.848 55.776-40.576l115.872-240.384 115.84 240.416c10.496 21.728 31.424 36.928 55.744 40.576l266.496 39.68-195.264 194.912z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["star","favorite"],"grid":32},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59706,"name":"control-Stars"},"setIdx":5,"setId":1,"iconIdx":99},{"icon":{"paths":["M66.337 575.491l276.668-171.531v-57.177l-331.627 207.614v42.189l331.627 207.614-0-57.177z","M957.663 575.49l-276.668-171.531v-57.177l331.627 207.614v42.189l-331.627 207.614 0-57.177z","M583.295 214.183l-200.825 621.623 53.007 17.527 200.837-621.623z"],"attrs":[{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["prerender"]},"attrs":[{},{},{}],"properties":{"order":114,"id":1,"name":"prerender","prevSize":32,"code":59724},"setIdx":4,"setId":2,"iconIdx":0},{"icon":{"paths":["M1024 512c0 282.77-229.23 512-512 512s-512-229.23-512-512c0-282.77 229.23-512 512-512s512 229.23 512 512z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["circle"]},"attrs":[{}],"properties":{"order":106,"id":0,"name":"circle","prevSize":32,"code":59729},"setIdx":5,"setId":1,"iconIdx":26},{"icon":{"paths":["M512 0c-15.36 0-25.6 10.24-25.6 25.6s10.24 25.6 25.6 25.6h128v870.4h-128c-15.36 0-25.6 10.24-25.6 25.6s10.24 25.6 25.6 25.6h307.2c15.36 0 25.6-10.24 25.6-25.6s-10.24-25.6-25.6-25.6h-128v-870.4h128c15.36 0 25.6-10.24 25.6-25.6s-10.24-25.6-25.6-25.6h-307.2zM51.2 204.8c-28.16 0-51.2 23.040-51.2 51.2v460.8c0 28.16 23.040 51.2 51.2 51.2h537.6v-51.2h-512c-15.36 0-25.6-10.24-25.6-25.6v-409.6c0-15.36 10.24-25.6 25.6-25.6h512v-51.2h-537.6zM742.4 204.8v51.2h204.8c15.36 0 25.6 10.24 25.6 25.6v409.6c0 15.36-10.24 25.6-25.6 25.6h-204.8v51.2h230.4c28.16 0 51.2-23.040 51.2-51.2v-460.8c0-28.16-23.040-51.2-51.2-51.2h-230.4z","M386.56 606.72c0 12.8-7.68 23.040-20.48 25.6-28.16 10.24-58.88 15.36-92.16 15.36-35.84 0-66.56-10.24-84.48-25.6s-25.6-38.4-25.6-66.56 10.24-51.2 25.6-66.56c17.92-17.92 46.080-23.040 84.48-23.040h69.12v-38.4c0-35.84-25.6-53.76-64-53.76-23.040 0-46.080 7.68-69.12 20.48-2.56 2.56-5.12 2.56-10.24 2.56-10.24 0-20.48-7.68-20.48-20.48 0-7.68 2.56-12.8 10.24-17.92 30.72-20.48 61.44-25.6 92.16-25.6 56.32 0 104.96 30.72 104.96 92.16v181.76zM345.6 501.76h-69.12c-61.44 0-69.12 28.16-69.12 53.76s7.68 56.32 69.12 56.32c23.040 0 46.080-2.56 69.12-10.24v-99.84z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-Slug"]},"attrs":[{},{}],"properties":{"order":103,"id":0,"name":"control-Slug","prevSize":32,"code":59727},"setIdx":5,"setId":1,"iconIdx":28},{"icon":{"paths":["M295.954 822.751h-94.705c-47.353 0-88.786-41.434-88.786-88.786v-491.283c0-47.353 41.434-88.786 88.786-88.786h94.705v-59.191h-94.705c-82.867 0-147.977 65.11-147.977 147.977v491.283c0 82.867 65.11 147.977 147.977 147.977h94.705v-59.191z","M970.728 473.526c-82.867-171.653-201.249-378.821-272.277-378.821h-112.462v59.191h112.462c35.514 11.838 136.139 177.572 213.087 337.387-76.948 153.896-177.572 325.549-213.087 337.387h-112.462v59.191h112.462c71.029 0 183.491-207.168 272.277-384.74l5.919-11.838-5.919-17.757z","M266.358 337.341v260.462h59.191v-260.462z","M479.422 337.341v260.462h59.191v-260.462z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["Tags"]},"attrs":[{},{},{},{}],"properties":{"order":98,"id":0,"name":"type-Tags","prevSize":32,"code":59722},"setIdx":5,"setId":1,"iconIdx":30},{"icon":{"paths":["M512 102.4c-200.4 0-366.954 144.072-402.4 334.2-0.031 0.165-0.069 0.335-0.1 0.5-2.974 16.061-4.76 32.441-5.8 49.1-0.017 0.271-0.084 0.529-0.1 0.8 0.019 0.004 0.080-0.004 0.1 0-0.503 8.31-1.3 16.564-1.3 25 0 226.202 183.398 409.6 409.6 409.6 208.165 0 379.707-155.44 405.8-356.5 0.004-0.033-0.004-0.067 0-0.1 1.94-14.978 3.124-30.16 3.4-45.6 0.044-2.487 0.4-4.903 0.4-7.4 0-226.202-183.398-409.6-409.6-409.6zM512 153.6c185.461 0 337.902 140.924 356.4 321.5-35.181-21.812-84.232-39.9-151.6-39.9-85.35 0-140.891 41.606-194.6 81.9-49.152 36.864-95.55 71.7-163.8 71.7-86.067 0-135.862-54.67-175.9-98.6-9.001-9.901-17.11-17.483-25.4-25.3 23.131-175.603 172.981-311.3 354.9-311.3zM716.8 486.4c77.828 0 125.173 28.221 152.2 52.8-13.96 185.173-168.254 331.2-357 331.2-190.097 0-345.175-148.14-357.2-335.2 41.826 45.372 102.577 104.8 203.6 104.8 85.35 0 140.891-41.606 194.6-81.9 49.152-36.915 95.55-71.7 163.8-71.7z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["activity"]},"attrs":[{}],"properties":{"order":12,"id":36,"name":"activity, history, time","prevSize":32,"code":59652},"setIdx":5,"setId":1,"iconIdx":31},{"icon":{"paths":["M512 0c-35.392 0-64 28.608-64 64v384h-384c-35.392 0-64 28.608-64 64s28.608 64 64 64h384v384c0 35.392 28.608 64 64 64s64-28.608 64-64v-384h384c35.392 0 64-28.608 64-64s-28.608-64-64-64h-384v-384c0-35.392-28.608-64-64-64z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["add"]},"attrs":[{}],"properties":{"order":13,"id":35,"name":"add, plus","prevSize":32,"code":59653},"setIdx":5,"setId":1,"iconIdx":32},{"icon":{"paths":["M512 102.4c-226.202 0-409.6 183.398-409.6 409.6s183.398 409.6 409.6 409.6c226.202 0 409.6-183.398 409.6-409.6s-183.398-409.6-409.6-409.6zM512 153.6c197.632 0 358.4 160.819 358.4 358.4s-160.768 358.4-358.4 358.4c-197.632 0-358.4-160.819-358.4-358.4s160.768-358.4 358.4-358.4zM691.9 333c-12.893 0.002-25.782 4.882-35.5 14.6l-222.2 221.9-67.7-67.5c-19.19-19.294-51.085-19.215-70.3 0-19.15 19.15-19.15 51.050 0 70.2 0.198 0.2 26.198 26.681 52 53 12.95 13.209 25.761 26.372 35.2 36 4.719 4.814 8.607 8.755 11.2 11.4 1.296 1.322 2.293 2.281 2.9 2.9 0.279 0.282 0.488 0.486 0.6 0.6 0.001 0.001 7.591-7.429 14.6-14.3l-14.5 14.4 0.2 0.2v0.1c19.43 19.327 51.57 19.327 71 0v-0.1l258.1-257.6c19.546-19.447 19.521-51.885-0.1-71.3-9.731-9.679-22.607-14.502-35.5-14.5z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["check-circle"]},"attrs":[{}],"properties":{"order":14,"id":34,"name":"check-circle","prevSize":32,"code":59654},"setIdx":5,"setId":1,"iconIdx":33},{"icon":{"paths":["M512 1024c-282.778 0-512-229.222-512-512s229.222-512 512-512 512 229.222 512 512-229.222 512-512 512zM855.808 270.592c-19.2-19.2-50.278-19.2-69.478 0l-376.73 376.73-171.878-171.93c-19.2-19.2-50.278-19.2-69.478 0s-19.2 50.278 0 69.478c0 0 201.523 205.261 204.8 208.486 9.984 10.138 23.347 14.643 36.557 14.080 13.21 0.563 26.573-3.942 36.608-14.029 3.277-3.226 409.6-413.286 409.6-413.286 19.2-19.2 19.2-50.33 0-69.53z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["check-circle-filled"]},"attrs":[{}],"properties":{"order":27,"id":33,"name":"check-circle-filled","prevSize":32,"code":59655},"setIdx":5,"setId":1,"iconIdx":34},{"icon":{"paths":["M601.024 512l276.736 276.736c24.512 24.576 24.512 64.384 0 89.024-24.64 24.576-64.384 24.576-89.024 0l-276.736-276.736-276.736 276.736c-24.512 24.576-64.384 24.576-89.024 0-24.512-24.64-24.512-64.448 0-89.024l276.736-276.736-276.736-276.736c-24.512-24.576-24.512-64.384 0-89.024 24.64-24.576 64.512-24.576 89.024 0l276.736 276.736 276.736-276.736c24.64-24.576 64.384-24.576 89.024 0 24.512 24.64 24.512 64.448 0 89.024l-276.736 276.736z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["close"]},"attrs":[{}],"properties":{"order":28,"id":32,"name":"close","prevSize":32,"code":59656},"setIdx":5,"setId":1,"iconIdx":35},{"icon":{"paths":["M409.6 435.2h-153.6v51.2h153.6v-51.2zM409.6 332.8h-153.6v51.2h153.6v-51.2zM256 691.2h409.6v-51.2h-409.6v51.2zM409.6 230.4h-153.6v51.2h153.6v-51.2zM870.4 179.2h-51.2v-51.2c0-28.262-22.938-51.2-51.2-51.2h-614.4c-28.262 0-51.2 22.938-51.2 51.2v665.6c0 28.262 22.938 51.2 51.2 51.2h51.2v51.2c0 28.262 22.938 51.2 51.2 51.2h614.4c28.262 0 51.2-22.938 51.2-51.2v-665.6c0-28.262-22.938-51.2-51.2-51.2zM179.2 793.6c-14.157 0-25.6-11.443-25.6-25.6v-614.4c0-14.131 11.443-25.6 25.6-25.6h563.2c14.157 0 25.6 11.469 25.6 25.6v614.4c0 14.157-11.443 25.6-25.6 25.6h-563.2zM870.4 870.4c0 14.157-11.443 25.6-25.6 25.6h-563.2c-14.157 0-25.6-11.443-25.6-25.6v-25.6h512c28.262 0 51.2-22.938 51.2-51.2v-563.2h25.6c14.157 0 25.6 11.469 25.6 25.6v614.4zM614.4 230.4h-102.4c-28.262 0-51.2 22.938-51.2 51.2v153.6c0 28.262 22.938 51.2 51.2 51.2h102.4c28.262 0 51.2-22.938 51.2-51.2v-153.6c0-28.262-22.938-51.2-51.2-51.2zM614.4 435.2h-102.4v-153.6h102.4v153.6zM256 588.8h409.6v-51.2h-409.6v51.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["content"]},"attrs":[{}],"properties":{"order":37,"id":31,"name":"type-References","prevSize":32,"code":59657},"setIdx":5,"setId":1,"iconIdx":36},{"icon":{"paths":["M793.6 844.8c0 14.157-11.443 25.6-25.6 25.6h-665.6c-14.131 0-25.6-11.443-25.6-25.6v-665.6c0-14.157 11.469-25.6 25.6-25.6h665.6c14.157 0 25.6 11.443 25.6 25.6v102.4h51.2v-128c0-28.262-22.938-51.2-51.2-51.2h-716.8c-28.262 0-51.2 22.938-51.2 51.2v716.8c0 28.262 22.938 51.2 51.2 51.2h716.8c28.262 0 51.2-22.938 51.2-51.2v-281.6h-51.2v256zM991.078 237.747c-9.958-9.958-26.035-9.958-35.968 0l-391.91 391.91-238.31-238.31c-9.958-9.958-26.061-9.958-35.942 0-9.958 9.907-9.958 26.010 0 35.942l254.874 254.874c0.461 0.538 0.614 1.203 1.126 1.69 5.043 5.018 11.674 7.475 18.278 7.373 6.605 0.102 13.235-2.355 18.278-7.373 0.512-0.512 0.666-1.178 1.126-1.69l408.448-408.474c9.933-9.933 9.933-26.035 0-35.942z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-checkbox"]},"attrs":[{}],"properties":{"order":38,"id":30,"name":"control-Checkbox","prevSize":32,"code":59658},"setIdx":5,"setId":1,"iconIdx":37},{"icon":{"paths":["M51.2 0c-28.262 0-51.2 22.938-51.2 51.2v281.6c0 28.262 22.938 51.2 51.2 51.2h921.6c28.262 0 51.2-22.938 51.2-51.2v-281.6c0-28.262-22.938-51.2-51.2-51.2h-921.6zM76.8 51.2h512v281.6h-512c-14.157 0-25.6-11.443-25.6-25.6v-230.4c0-14.157 11.443-25.6 25.6-25.6zM640 51.2h307.2c14.157 0 25.6 11.443 25.6 25.6v230.4c0 14.157-11.443 25.6-25.6 25.6h-307.2v-281.6zM716.8 153.6c-0.41 0.358 89.139 102.938 89.6 102.4 0.512 0 89.6-95.36 89.6-102.4 0 0.384-172.16 0-179.2 0zM128 435.2c-42.394 0-76.8 34.406-76.8 76.8s34.406 76.8 76.8 76.8c42.394 0 76.8-34.406 76.8-76.8s-34.406-76.8-76.8-76.8zM128 486.4c14.157 0 25.6 11.443 25.6 25.6s-11.443 25.6-25.6 25.6c-14.157 0-25.6-11.443-25.6-25.6s11.443-25.6 25.6-25.6zM307.2 486.4c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h640c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-640zM128 640c-42.394 0-76.8 34.381-76.8 76.8s34.406 76.8 76.8 76.8c42.394 0 76.8-34.381 76.8-76.8s-34.406-76.8-76.8-76.8zM128 691.2c14.157 0 25.6 11.443 25.6 25.6s-11.443 25.6-25.6 25.6c-14.157 0-25.6-11.443-25.6-25.6s11.443-25.6 25.6-25.6zM307.2 691.2c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h640c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-640zM128 844.8c-42.394 0-76.8 34.381-76.8 76.8s34.406 76.8 76.8 76.8c42.394 0 76.8-34.381 76.8-76.8s-34.406-76.8-76.8-76.8zM128 896c14.157 0 25.6 11.443 25.6 25.6s-11.443 25.6-25.6 25.6c-14.157 0-25.6-11.443-25.6-25.6s11.443-25.6 25.6-25.6zM307.2 896c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h640c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-640z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-dropdown"]},"attrs":[{}],"properties":{"order":39,"id":29,"name":"control-Dropdown","prevSize":32,"code":59659},"setIdx":5,"setId":1,"iconIdx":38},{"icon":{"paths":["M512 0c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h128v870.4h-128c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h307.2c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-128v-870.4h128c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-307.2zM51.2 204.8c-28.262 0-51.2 22.938-51.2 51.2v460.8c0 28.262 22.938 51.2 51.2 51.2h537.6v-51.2h-512c-14.131 0-25.6-11.443-25.6-25.6v-409.6c0-14.157 11.469-25.6 25.6-25.6h512v-51.2h-537.6zM742.4 204.8v51.2h204.8c14.157 0 25.6 11.443 25.6 25.6v409.6c0 14.157-11.443 25.6-25.6 25.6h-204.8v51.2h230.4c28.262 0 51.2-22.938 51.2-51.2v-460.8c0-28.262-22.938-51.2-51.2-51.2h-230.4zM285.9 307c-0.589 0.051-1.161 0.048-1.75 0.15-8.243 0.051-16.396 4.474-20.85 13.050l-132.55 306.25c-6.656 12.749-2.866 28.981 8.5 36.2 11.341 7.219 25.97 2.749 32.6-10l27.65-63.85h170.5c0.512 0 0.914-0.224 1.4-0.25l27.45 64.050c6.63 12.749 21.136 17.269 32.4 10.050s15.005-23.451 8.4-36.2l-131.3-306.25c-4.454-8.576-12.432-12.973-20.65-13.050-0.614-0.102-1.211-0.099-1.8-0.15zM285.9 389.15l63.65 148.45h-127.9l64.25-148.45z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-input"]},"attrs":[{}],"properties":{"order":41,"id":28,"name":"control-Input","prevSize":32,"code":59660},"setIdx":5,"setId":1,"iconIdx":39},{"icon":{"paths":["M153.6 716.8c-84.787 0-153.6 68.813-153.6 153.6s68.813 153.6 153.6 153.6c84.787 0 153.6-68.813 153.6-153.6s-68.813-153.6-153.6-153.6zM153.6 972.8c-56.55 0-102.4-45.85-102.4-102.4s45.85-102.4 102.4-102.4c56.55 0 102.4 45.85 102.4 102.4s-45.85 102.4-102.4 102.4zM384 179.2h614.4c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-614.4c-14.131 0-25.6 11.443-25.6 25.6s11.469 25.6 25.6 25.6zM998.4 486.4h-614.4c-14.131 0-25.6 11.443-25.6 25.6s11.469 25.6 25.6 25.6h614.4c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6zM153.6 0c-84.787 0-153.6 68.787-153.6 153.6s68.813 153.6 153.6 153.6c84.787 0 153.6-68.787 153.6-153.6s-68.813-153.6-153.6-153.6zM153.6 256c-56.55 0-102.4-45.85-102.4-102.4s45.85-102.4 102.4-102.4c56.55 0 102.4 45.85 102.4 102.4s-45.85 102.4-102.4 102.4zM153.6 358.4c-84.787 0-153.6 68.787-153.6 153.6 0 84.787 68.813 153.6 153.6 153.6s153.6-68.813 153.6-153.6c0-84.813-68.813-153.6-153.6-153.6zM153.6 614.4c-56.55 0-102.4-45.85-102.4-102.4s45.85-102.4 102.4-102.4c56.55 0 102.4 45.85 102.4 102.4s-45.85 102.4-102.4 102.4zM153.6 102.4c-28.262 0-51.2 22.938-51.2 51.2s22.938 51.2 51.2 51.2c28.262 0 51.2-22.938 51.2-51.2s-22.938-51.2-51.2-51.2zM998.4 844.8h-614.4c-14.131 0-25.6 11.443-25.6 25.6s11.469 25.6 25.6 25.6h614.4c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-radio"]},"attrs":[{}],"properties":{"order":42,"id":27,"name":"control-Radio","prevSize":32,"code":59661},"setIdx":5,"setId":1,"iconIdx":40},{"icon":{"paths":["M0 0v204.8h76.8v76.8h51.2v-76.8h76.8v-204.8h-204.8zM819.2 0v204.8h204.8v-204.8h-204.8zM51.2 51.2h102.4v102.4h-102.4v-102.4zM870.4 51.2h102.4v102.4h-102.4v-102.4zM281.6 76.8v51.2h102.4v-51.2h-102.4zM486.4 76.8v51.2h102.4v-51.2h-102.4zM691.2 76.8v51.2h102.4v-51.2h-102.4zM333.25 204.8c-7.091-0.307-14.348 2.097-19.75 7.55l-74.75 74.75c-10.317 10.291-10.317 27.083 0 37.4s27.059 10.317 37.35 0l68.45-68.5h141.85v486.4h-50.7c-7.117-0.307-14.348 2.097-19.75 7.55l-23.6 23.55c-10.317 10.317-10.317 27.083 0 37.4 10.291 10.317 27.109 10.317 37.4 0l17.25-17.3h129.75l18.050 18c10.394 10.368 27.181 10.368 37.6 0 10.368-10.394 10.368-27.181 0-37.6l-24-24c-5.478-5.478-12.682-7.907-19.85-7.6h-50.95v-486.4h141.55l69.25 69.2c10.394 10.368 27.155 10.368 37.6 0 10.368-10.368 10.368-27.181 0-37.6l-75.2-75.2c-5.478-5.478-12.706-7.907-19.9-7.6h-357.65zM896 281.6v102.4h51.2v-102.4h-51.2zM76.8 384v102.4h51.2v-102.4h-51.2zM896 486.4v102.4h51.2v-102.4h-51.2zM76.8 588.8v102.4h51.2v-102.4h-51.2zM896 691.2v102.4h51.2v-102.4h-51.2zM76.8 793.6v25.6h-76.8v204.8h204.8v-76.8h76.8v-51.2h-76.8v-76.8h-76.8v-25.6h-51.2zM819.2 819.2v76.8h-25.6v51.2h25.6v76.8h204.8v-204.8h-204.8zM51.2 870.4h102.4v102.4h-102.4v-102.4zM870.4 870.4h102.4v102.4h-102.4v-102.4zM384 896v51.2h102.4v-51.2h-102.4zM588.8 896v51.2h102.4v-51.2h-102.4z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-textarea"]},"attrs":[{}],"properties":{"order":17,"id":26,"name":"control-TextArea","prevSize":32,"code":59662},"setIdx":5,"setId":1,"iconIdx":41},{"icon":{"paths":["M332.8 25.6c-127.258 0-230.4 103.142-230.4 230.4s103.142 230.4 230.4 230.4h358.4c127.258 0 230.4-103.142 230.4-230.4s-103.142-230.4-230.4-230.4h-358.4zM332.8 76.8h358.4c98.97 0 179.2 80.23 179.2 179.2s-80.23 179.2-179.2 179.2h-358.4c-98.97 0-179.2-80.23-179.2-179.2s80.23-179.2 179.2-179.2zM332.8 128c-70.707 0-128 57.293-128 128s57.293 128 128 128c70.707 0 128-57.293 128-128s-57.293-128-128-128zM332.8 179.2c42.419 0 76.8 34.381 76.8 76.8s-34.381 76.8-76.8 76.8c-42.419 0-76.8-34.381-76.8-76.8s34.381-76.8 76.8-76.8zM332.8 537.6c-127.258 0-230.4 103.142-230.4 230.4s103.142 230.4 230.4 230.4h358.4c127.258 0 230.4-103.142 230.4-230.4s-103.142-230.4-230.4-230.4h-358.4zM332.8 588.8h358.4c98.97 0 179.2 80.23 179.2 179.2s-80.23 179.2-179.2 179.2h-358.4c-98.97 0-179.2-80.23-179.2-179.2s80.23-179.2 179.2-179.2zM691.2 640c-70.707 0-128 57.293-128 128s57.293 128 128 128c70.707 0 128-57.293 128-128s-57.293-128-128-128zM691.2 691.2c42.419 0 76.8 34.381 76.8 76.8s-34.381 76.8-76.8 76.8c-42.419 0-76.8-34.381-76.8-76.8s34.381-76.8 76.8-76.8z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-toggle"]},"attrs":[{}],"properties":{"order":16,"id":25,"name":"control-Toggle","prevSize":32,"code":59663},"setIdx":5,"setId":1,"iconIdx":42},{"icon":{"paths":["M204.8 51.2c-56.525 0-102.4 45.875-102.4 102.4v512c0 56.525 45.875 102.4 102.4 102.4h409.6c56.525 0 102.4-45.875 102.4-102.4v-512c0-56.525-45.875-102.4-102.4-102.4h-409.6zM204.8 102.4h409.6c28.262 0 51.2 22.886 51.2 51.2v512c0 28.314-22.938 51.2-51.2 51.2h-409.6c-28.262 0-51.2-22.886-51.2-51.2v-512c0-28.314 22.938-51.2 51.2-51.2zM768 204.8v51.2c28.262 0 51.2 22.886 51.2 51.2v512c0 28.314-22.938 51.2-51.2 51.2h-409.6c-28.262 0-51.2-22.886-51.2-51.2h-51.2c0 56.525 45.875 102.4 102.4 102.4h409.6c56.525 0 102.4-45.875 102.4-102.4v-512c0-56.525-45.875-102.4-102.4-102.4z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["copy"]},"attrs":[{}],"properties":{"order":90,"id":24,"name":"copy","prevSize":32,"code":59664},"setIdx":5,"setId":1,"iconIdx":43},{"icon":{"paths":["M828.8 1024h-633.6c-105.6 0-195.2-89.6-195.2-195.2v-320c0-281.6 227.2-508.8 505.6-508.8 288 0 518.4 230.4 518.4 518.4v310.4c0 105.6-89.6 195.2-195.2 195.2zM505.6 64c-243.2 0-441.6 198.4-441.6 441.6v320c0 73.6 60.8 134.4 131.2 134.4h630.4c73.6 0 131.2-60.8 131.2-131.2v-310.4c3.2-249.6-201.6-454.4-451.2-454.4z","M512 668.8c-3.2 0-6.4 0-6.4 0-32-3.2-64-19.2-80-48l-192-278.4c-9.6-9.6-9.6-25.6-0-38.4 9.6-9.6 25.6-12.8 38.4-6.4l294.4 172.8c28.8 16 48 44.8 51.2 76.8s-6.4 64-28.8 89.6c-19.2 22.4-48 32-76.8 32zM364.8 428.8l108.8 160c6.4 9.6 19.2 19.2 32 19.2s25.6-3.2 35.2-12.8c9.6-9.6 12.8-22.4 9.6-35.2s-9.6-22.4-19.2-32l-166.4-99.2z","M678.4 364.8c-6.4 0-12.8-3.2-19.2-6.4-16-9.6-19.2-28.8-9.6-44.8l54.4-83.2c9.6-16 28.8-19.2 44.8-9.6 19.2 12.8 22.4 35.2 12.8 48l-54.4 83.2c-6.4 9.6-16 12.8-28.8 12.8z"],"attrs":[{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["dashboard"]},"attrs":[{},{},{}],"properties":{"order":26,"id":23,"name":"dashboard","prevSize":32,"code":59665},"setIdx":5,"setId":1,"iconIdx":44},{"icon":{"paths":["M597.35 819.2c14.131 0 25.6-11.469 25.6-25.6v-307.2c0-14.080-11.469-25.6-25.6-25.6s-25.6 11.52-25.6 25.6v307.2c0 14.131 11.418 25.6 25.6 25.6zM776.55 204.8h-153.6v-51.2c0-28.314-22.886-51.2-51.2-51.2h-102.4c-28.262 0-51.2 22.886-51.2 51.2v51.2h-153.6c-28.262 0-51.2 22.886-51.2 51.2v102.4c0 28.314 22.938 51.2 51.2 51.2v460.8c0 28.314 22.938 51.2 51.2 51.2h409.6c28.314 0 51.2-22.886 51.2-51.2v-460.8c28.314 0 51.2-22.886 51.2-51.2v-102.4c0-28.314-22.938-51.2-51.2-51.2zM469.35 153.6h102.4v51.2h-102.4v-51.2zM725.35 870.4h-409.6v-460.8h409.6v460.8zM776.55 358.4h-512v-102.4h512v102.4zM443.75 819.2c14.131 0 25.6-11.469 25.6-25.6v-307.2c0-14.080-11.469-25.6-25.6-25.6s-25.6 11.52-25.6 25.6v307.2c0 14.131 11.469 25.6 25.6 25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["delete"]},"attrs":[{}],"properties":{"order":29,"id":22,"name":"delete, bin","prevSize":32,"code":59666},"setIdx":5,"setId":1,"iconIdx":45},{"icon":{"paths":["M832 128h-192v-64c0-35.392-28.608-64-64-64h-128c-35.328 0-64 28.608-64 64v64h-192c-35.328 0-64 28.608-64 64v128c0 35.392 28.672 64 64 64v512c0 35.392 28.672 64 64 64h512c35.392 0 64-28.608 64-64v-512c35.392 0 64-28.608 64-64v-128c0-35.392-28.608-64-64-64zM448 64h128v64h-128v-64zM448 800c0 17.664-14.336 32-32 32s-32-14.336-32-32v-320c0-17.6 14.336-32 32-32s32 14.4 32 32v320zM640 800c0 17.664-14.336 32-32 32s-32-14.336-32-32v-320c0-17.6 14.336-32 32-32s32 14.4 32 32v320zM832 320h-640v-128h640v128z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["delete-filled"]},"attrs":[{}],"properties":{"order":36,"id":21,"name":"delete-filled","prevSize":32,"code":59667},"setIdx":5,"setId":1,"iconIdx":46},{"icon":{"paths":["M358.4 102.4c-28.314 0-51.2 22.886-51.2 51.2v256h51.2v-256h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-358.4v51.2h358.4c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-343.4zM716.8 189.8l117.4 117.4h-117.4v-117.4zM332.8 460.8c-127.232 0-230.4 103.168-230.4 230.4s103.168 230.4 230.4 230.4c127.232 0 230.4-103.168 230.4-230.4s-103.168-230.4-230.4-230.4zM332.8 512c98.816 0 179.2 80.384 179.2 179.2s-80.384 179.2-179.2 179.2c-98.816 0-179.2-80.384-179.2-179.2s80.384-179.2 179.2-179.2zM227.2 665.6c-12.39 0-22.4 10.061-22.4 22.4v6.4c0 12.39 10.010 22.4 22.4 22.4h211.2c12.39 0 22.4-10.010 22.4-22.4v-6.4c0-12.39-10.061-22.4-22.4-22.4h-211.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["document-delete"]},"attrs":[{}],"properties":{"order":35,"id":20,"name":"document-delete","prevSize":32,"code":59668},"setIdx":5,"setId":1,"iconIdx":47},{"icon":{"paths":["M358.4 102.4c-28.314 0-51.2 22.886-51.2 51.2v256h51.2v-256h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-358.4v51.2h358.4c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-343.4zM716.8 189.8l117.4 117.4h-117.4v-117.4zM332.8 460.8c-127.232 0-230.4 103.168-230.4 230.4s103.168 230.4 230.4 230.4c127.232 0 230.4-103.168 230.4-230.4s-103.168-230.4-230.4-230.4zM332.8 512c39.934 0 76.475 13.533 106.3 35.7l-250.4 249c-21.807-29.683-35.1-65.924-35.1-105.5 0-98.816 80.384-179.2 179.2-179.2zM477 585.7c21.785 29.674 35 65.947 35 105.5 0 98.816-80.384 179.2-179.2 179.2-39.906 0-76.386-13.561-106.2-35.7l250.4-249z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["document-disable"]},"attrs":[{}],"properties":{"order":40,"id":19,"name":"document-disable","prevSize":32,"code":59669},"setIdx":5,"setId":1,"iconIdx":48},{"icon":{"paths":["M358.4 102.4c-28.314 0-51.2 22.886-51.2 51.2v256h51.2v-256h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-358.4v51.2h358.4c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-343.4zM716.8 189.8l117.4 117.4h-117.4v-117.4zM332.8 460.8l-230.4 256v51.2h102.4v153.6h256v-153.6h102.4v-51.2l-230.4-256zM332.8 537.3l161.5 179.5h-84.7v153.6h-153.6v-153.6h-84.7l161.5-179.5z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["document-publish"]},"attrs":[{}],"properties":{"order":44,"id":18,"name":"document-publish","prevSize":32,"code":59670},"setIdx":5,"setId":1,"iconIdx":49},{"icon":{"paths":["M665.6 51.2v102.4h102.4v-102.4h-102.4zM460.8 153.6h102.4v-102.4h-102.4v102.4zM460.8 358.4h102.4v-102.4h-102.4v102.4zM665.6 358.4h102.4v-102.4h-102.4v102.4zM665.6 563.2h102.4v-102.4h-102.4v102.4zM460.8 563.2h102.4v-102.4h-102.4v102.4zM460.8 768h102.4v-102.4h-102.4v102.4zM665.6 768h102.4v-102.4h-102.4v102.4zM665.6 972.8h102.4v-102.4h-102.4v102.4zM460.8 972.8h102.4v-102.4h-102.4v102.4zM256 153.6h102.4v-102.4h-102.4v102.4zM256 358.4h102.4v-102.4h-102.4v102.4zM256 563.2h102.4v-102.4h-102.4v102.4zM256 768h102.4v-102.4h-102.4v102.4zM256 972.8h102.4v-102.4h-102.4v102.4z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["drag"]},"attrs":[{}],"properties":{"order":43,"id":17,"name":"drag","prevSize":32,"code":59671},"setIdx":5,"setId":1,"iconIdx":50},{"icon":{"paths":["M921.6 281.958c0-70.707-171.878-128.154-384-128.154s-384 57.19-384 127.898c0 10.035 3.789 19.712 10.342 29.030 0-0.051 296.858 406.067 296.858 406.067v256l153.6-51.2v-204.8c0 0 298.752-408.166 299.725-409.702 0 0 7.475-16.64 7.475-25.139zM537.6 204.8c206.899 0 318.208 53.248 332.083 76.8-13.875 23.552-125.184 76.8-332.083 76.8s-318.208-53.248-332.083-76.8c13.875-23.552 125.184-76.8 332.083-76.8zM869.376 345.856v0 0zM573.030 686.592c-6.4 8.755-9.83 19.354-9.83 30.208v167.885l-51.2 17.050v-184.934c0-10.854-3.43-21.453-9.83-30.208l-228.147-312.115c68.762 21.709 161.382 35.123 263.578 35.123 102.298 0 195.021-13.414 263.834-35.174-0.102 0.051-0.205 0.051-0.307 0.102l-228.096 312.064z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["filter"]},"attrs":[{}],"properties":{"order":18,"id":16,"name":"filter","prevSize":32,"code":59672},"setIdx":5,"setId":1,"iconIdx":51},{"icon":{"paths":["M512 0c-282.88 0-512 229.248-512 512 0 226.24 146.688 418.112 350.080 485.76 25.6 4.8 35.008-11.008 35.008-24.64 0-12.16-0.448-44.352-0.64-87.040-142.464 30.912-172.48-68.672-172.48-68.672-23.296-59.136-56.96-74.88-56.96-74.88-46.4-31.744 3.584-31.104 3.584-31.104 51.392 3.584 78.4 52.736 78.4 52.736 45.696 78.272 119.872 55.68 149.12 42.56 4.608-33.088 17.792-55.68 32.448-68.48-113.728-12.8-233.216-56.832-233.216-252.992 0-55.872 19.84-101.568 52.672-137.408-5.76-12.928-23.040-64.96 4.48-135.488 0 0 42.88-13.76 140.8 52.48 40.96-11.392 84.48-17.024 128-17.28 43.52 0.256 87.040 5.888 128 17.28 97.28-66.24 140.16-52.48 140.16-52.48 27.52 70.528 10.24 122.56 5.12 135.488 32.64 35.84 52.48 81.536 52.48 137.408 0 196.672-119.68 240-233.6 252.608 17.92 15.36 34.56 46.72 34.56 94.72 0 68.48-0.64 123.52-0.64 140.16 0 13.44 8.96 29.44 35.2 24.32 204.864-67.136 351.424-259.136 351.424-485.056 0-282.752-229.248-512-512-512z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["brand","github"],"grid":0},"attrs":[{}],"properties":{"order":77,"id":0,"name":"github","prevSize":32,"code":59713},"setIdx":5,"setId":1,"iconIdx":52},{"icon":{"paths":["M512 512h-204.8v51.2h204.8v-51.2zM768 153.6h-51.2c0-28.314-22.886-51.2-51.2-51.2h-307.2c-28.314 0-51.2 22.886-51.2 51.2h-51.2c-28.314 0-51.2 22.886-51.2 51.2v665.6c0 28.314 22.886 51.2 51.2 51.2h512c28.314 0 51.2-22.886 51.2-51.2v-665.6c0-28.314-22.886-51.2-51.2-51.2zM358.4 153.6h307.2v51.2h-307.2v-51.2zM768 819.2c0 28.314-22.886 51.2-51.2 51.2h-409.6c-28.314 0-51.2-22.886-51.2-51.2v-563.2c0-28.314 22.886-51.2 51.2-51.2 0 28.314 22.886 51.2 51.2 51.2h307.2c28.314 0 51.2-22.886 51.2-51.2 28.314 0 51.2 22.886 51.2 51.2v563.2zM307.2 460.8h409.6v-51.2h-409.6v51.2zM307.2 665.6h409.6v-51.2h-409.6v51.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["help"]},"attrs":[{}],"properties":{"order":19,"id":15,"name":"help","prevSize":32,"code":59673},"setIdx":5,"setId":1,"iconIdx":53},{"icon":{"paths":["M512 0c-169.421 0-307.2 137.779-307.2 307.2 0 78.643 15.258 164.915 45.261 256.41 23.859 72.55 56.986 148.582 98.56 226.099 70.707 131.635 140.339 220.774 143.309 224.512 4.813 6.195 12.288 9.779 20.070 9.779 7.834 0 15.258-3.584 20.122-9.779 2.97-3.686 72.602-92.826 143.309-224.512 41.574-77.517 74.701-153.549 98.56-226.099 29.952-91.494 45.21-177.766 45.21-256.41 0-169.421-137.83-307.2-307.2-307.2zM630.682 764.672c-46.234 86.374-92.979 154.982-118.682 190.822-25.6-35.635-72.038-103.885-118.221-189.952-62.874-117.146-137.779-291.738-137.779-458.342 0-141.158 114.842-256 256-256s256 114.842 256 256c0 166.298-74.65 340.582-137.318 457.472zM512 153.6c-84.685 0-153.6 68.915-153.6 153.6s68.915 153.6 153.6 153.6 153.6-68.915 153.6-153.6-68.915-153.6-153.6-153.6zM512 409.6c-56.525 0-102.4-45.875-102.4-102.4 0-56.474 45.875-102.4 102.4-102.4 56.474 0 102.4 45.926 102.4 102.4 0 56.525-45.926 102.4-102.4 102.4z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["location"]},"attrs":[{}],"properties":{"order":25,"id":13,"name":"location, control-Map, type-Geolocation","prevSize":32,"code":59675},"setIdx":5,"setId":1,"iconIdx":54},{"icon":{"paths":["M512.273 83.782c-0.141 0.056-182.959 84.073-229.418 256.782-4.481 16.584 32.696 9.296 31.036 27.527-2.034 22.136-44.668 31.201-39.109 94.764 5.659 64.734 60.321 130.141 68.527 169.673v27.655c-0.497 8.54-4.566 31.715-18.018 43.036-7.378 6.19-17.322 8.421-30.436 6.782-18.205-2.275-25.449-14.468-28.345-24.309-4.753-16.218-0.322-35.123 10.345-44 10.724-8.924 12.17-24.842 3.236-35.564-8.934-10.712-24.858-12.161-35.582-3.218-25.995 21.64-36.887 61.52-26.491 97 9.815 33.392 36.197 55.884 70.6 60.182 4.903 0.609 9.566 0.909 14 0.909 26.623 0 44.661-10.175 55.582-19.455 32.866-27.97 35.449-74.593 35.636-79.818 0.009-0.309 0.018-0.618 0.018-0.927v-21.218h0.109v-1.418c0-12.351 10.008-22.364 22.382-22.364 11.944 0 21.609 9.346 22.273 21.109v202.491c-0.206 2.912-2.536 29.892-17.891 42.945-7.368 6.274-17.384 8.53-30.545 6.873-18.214-2.275-25.476-14.468-28.364-24.291-4.762-16.228-0.322-35.151 10.345-44.018 10.724-8.933 12.188-24.833 3.255-35.564-8.924-10.694-24.876-12.161-35.6-3.218-26.013 21.631-36.887 61.52-26.491 97 9.796 33.392 36.197 55.893 70.6 60.2 4.903 0.609 9.566 0.891 14 0.891 26.623 0 44.671-10.156 55.564-19.436 32.875-27.97 35.458-74.611 35.636-79.836 0.019-0.328 0.018-0.609 0.018-0.909v-225.636l0.127-0.055v-1c0-12.595 10.219-22.8 22.836-22.8 12.349 0 22.333 9.824 22.727 22.073v227.418c0 0.309-0 0.591 0.018 0.909 0.187 5.216 2.779 51.866 35.655 79.836 10.912 9.28 28.959 19.436 55.582 19.436 4.443 0 9.088-0.282 13.982-0.891 34.394-4.307 60.804-26.818 70.6-60.2 10.405-35.48-0.487-75.36-26.491-97-10.743-8.943-26.676-7.466-35.6 3.218-8.934 10.74-7.488 26.63 3.236 35.564 10.668 8.868 15.135 27.79 10.364 44.018-2.878 9.823-10.159 22.015-28.364 24.291-13.105 1.648-23.050-0.592-30.418-6.782-13.508-11.358-17.558-34.657-18.036-43v-201.818c0.297-12.093 10.14-21.818 22.327-21.818 12.374 0 22.4 10.003 22.4 22.364v1.418h0.073v21.218c0 0.318-0 0.628 0.018 0.927 0.178 5.216 2.779 51.848 35.655 79.818 10.912 9.28 28.941 19.455 55.564 19.455 4.434 0 9.107-0.292 14-0.891 34.394-4.298 60.786-26.818 70.582-60.2 10.405-35.48-0.487-75.351-26.491-97-10.743-8.933-26.667-7.476-35.582 3.236-8.943 10.722-7.488 26.622 3.236 35.545 10.668 8.877 15.117 27.8 10.345 44.018-2.878 9.842-10.159 22.025-28.364 24.291-13.086 1.648-23.050-0.583-30.418-6.764-13.508-11.368-17.549-34.675-18.018-43v-21.018c5.305-54.103 63.095-107.777 69.091-176.364 5.531-63.563-37.121-72.627-39.145-94.764-1.669-18.232 35.498-10.944 31.036-27.527-46.468-172.709-229.269-256.726-229.4-256.782z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["logo"]},"attrs":[{}],"properties":{"order":31,"id":12,"name":"logo","prevSize":32,"code":59676},"setIdx":5,"setId":1,"iconIdx":55},{"icon":{"paths":["M947.2 0h-870.4c-42.342 0-76.8 34.458-76.8 76.8v870.4c0 42.342 34.458 76.8 76.8 76.8h870.4c42.342 0 76.8-34.458 76.8-76.8v-870.4c0-42.342-34.458-76.8-76.8-76.8zM972.8 947.2c0 14.157-11.443 25.6-25.6 25.6h-870.4c-14.131 0-25.6-11.443-25.6-25.6v-870.4c0-14.131 11.469-25.6 25.6-25.6h870.4c14.157 0 25.6 11.469 25.6 25.6v870.4zM665.6 460.8c56.448 0 102.4-45.926 102.4-102.4s-45.952-102.4-102.4-102.4c-56.448 0-102.4 45.926-102.4 102.4s45.952 102.4 102.4 102.4zM665.6 307.2c28.211 0 51.2 22.989 51.2 51.2s-22.989 51.2-51.2 51.2c-28.211 0-51.2-22.989-51.2-51.2s22.989-51.2 51.2-51.2zM896 102.4h-768c-14.131 0-25.6 11.469-25.6 25.6v614.4c0 14.157 11.469 25.6 25.6 25.6h768c14.157 0 25.6-11.443 25.6-25.6v-614.4c0-14.131-11.443-25.6-25.6-25.6zM153.6 716.8v-118.246l164.301-184.858c4.198-4.787 9.728-7.373 15.462-7.475 5.734-0.051 11.29 2.458 15.642 7.040l283.238 303.539h-478.643zM870.4 716.8h-168.090l-315.853-338.432c-14.285-15.334-33.331-23.603-53.709-23.347-20.326 0.256-39.219 9.011-53.094 24.627l-126.054 141.798v-367.846h716.8v563.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["media"]},"attrs":[{}],"properties":{"order":30,"id":11,"name":"media, type-Assets, trigger-AssetChanged","prevSize":32,"code":59677},"setIdx":5,"setId":1,"iconIdx":56},{"icon":{"paths":["M128 384c-70.656 0-128 57.344-128 128s57.344 128 128 128c70.656 0 128-57.344 128-128s-57.344-128-128-128zM512 384c-70.656 0-128 57.344-128 128s57.344 128 128 128c70.656 0 128-57.344 128-128s-57.344-128-128-128zM896 384c-70.656 0-128 57.344-128 128s57.344 128 128 128c70.656 0 128-57.344 128-128s-57.344-128-128-128z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["more"]},"attrs":[{}],"properties":{"order":34,"id":10,"name":"more, dots","prevSize":32,"code":59678},"setIdx":5,"setId":1,"iconIdx":57},{"icon":{"paths":["M877.12 311.104l-66.304 66.368-228.224-228.224 66.368-66.368c25.216-25.152 66.048-25.152 91.264 0l136.896 137.024c25.216 25.216 25.216 65.984 0 91.2zM760.896 427.392l-386.176 386.112c-25.216 25.28-66.048 25.28-91.264 0l-136.96-136.896c-25.216-25.28-25.216-66.112 0-91.264l386.24-386.24 228.16 228.288zM64 896v-191.872l191.936 191.872h-191.936z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["pencil"]},"attrs":[{}],"properties":{"order":47,"id":9,"name":"pencil","prevSize":32,"code":59679},"setIdx":5,"setId":1,"iconIdx":58},{"icon":{"paths":["M892.083 131.917c-73.523-73.498-193.152-73.498-266.65 0l-157.184 157.107c-9.958 10.035-9.958 26.214 0 36.275 10.061 9.984 26.24 9.984 36.25 0l157.133-157.107c53.504-53.555 140.672-53.555 194.176 0 53.581 53.504 53.581 140.672 0 194.176l-186.138 186.163c-53.53 53.581-140.672 53.581-194.176 0-10.086-10.010-26.24-10.010-36.275 0-10.035 10.086-10.035 26.189 0 36.25 36.787 36.736 84.992 55.117 133.325 55.117s96.589-18.432 133.376-55.117l186.163-186.214c73.498-73.472 73.498-193.152 0-266.65zM519.45 698.726l-157.082 157.082c-53.504 53.555-140.672 53.555-194.176 0-53.581-53.504-53.581-140.672 0-194.176l186.138-186.163c53.53-53.581 140.672-53.581 194.176 0 10.086 9.984 26.189 9.984 36.275 0 10.035-10.086 10.035-26.214 0-36.25-73.549-73.498-193.203-73.498-266.701 0l-186.163 186.163c-73.498 73.574-73.498 193.203 0 266.701 36.787 36.71 85.043 55.117 133.325 55.117 48.333 0 96.538-18.406 133.325-55.117l157.133-157.133c10.010-10.010 10.010-26.189 0-36.224-10.010-9.984-26.189-9.984-36.25 0z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["reference"]},"attrs":[{}],"properties":{"order":45,"id":8,"name":"reference","prevSize":32,"code":59680},"setIdx":5,"setId":1,"iconIdx":59},{"icon":{"paths":["M800 1024h-576c-124.8 0-224-99.2-224-224v-300.8c0-124.8 99.2-224 224-224h576c124.8 0 224 99.2 224 224v300.8c0 124.8-99.2 224-224 224zM224 339.2c-89.6 0-160 70.4-160 160v300.8c0 89.6 70.4 160 160 160h576c89.6 0 160-70.4 160-160v-300.8c0-89.6-70.4-160-160-160h-576z","M828.8 201.6h-633.6c-19.2 0-32-12.8-32-32s12.8-32 32-32h630.4c19.2 0 32 12.8 32 32s-12.8 32-28.8 32z","M716.8 64h-409.6c-19.2 0-32-12.8-32-32s12.8-32 32-32h412.8c19.2 0 32 12.8 32 32s-16 32-35.2 32z","M800 416v64c0 48-38.4 83.2-83.2 83.2h-409.6c-44.8 3.2-83.2-35.2-83.2-83.2v-64h-54.4v64c0 76.8 64 140.8 140.8 140.8h406.4c76.8 0 140.8-64 140.8-140.8v-64h-57.6z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["schemas"]},"attrs":[{},{},{},{}],"properties":{"order":46,"id":7,"name":"schemas","prevSize":32,"code":59681},"setIdx":5,"setId":1,"iconIdx":60},{"icon":{"paths":["M939.776 1003.776c-27.2 27.008-71.232 27.008-98.368 0l-168.96-168.96c-66.176 38.464-142.016 62.080-224 62.080-247.744 0-448.448-200.832-448.448-448.448 0-247.744 200.704-448.448 448.448-448.448 247.68 0 448.512 200.704 448.512 448.448 0 115.136-44.672 218.944-115.904 298.304l158.656 158.656c27.008 27.136 27.008 71.168 0.064 98.368zM448.448 128.128c-176.896 0-320.32 143.36-320.32 320.32s143.424 320.32 320.32 320.32c176.96 0 320.384-143.36 320.384-320.32s-143.488-320.32-320.384-320.32z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["search"]},"attrs":[{}],"properties":{"order":23,"id":6,"name":"search","prevSize":32,"code":59682},"setIdx":5,"setId":1,"iconIdx":61},{"icon":{"paths":["M1019.11 440.755c-1.946-13.747-14.438-23.398-28.16-21.888-16.947 1.843-34.253-0.589-50.048-7.091-52.25-21.504-77.261-81.459-55.757-133.709 6.605-15.846 16.947-29.85 30.208-40.602 10.803-8.653 12.698-24.294 4.352-35.354-28.902-37.99-62.797-71.706-100.838-100.045-10.701-8.090-25.805-6.451-34.662 3.661-28.8 33.254-75.546 44.262-116.198 27.546-40.704-16.742-66.099-57.498-63.206-101.453 0.845-13.338-8.755-25.19-21.99-27.008-47.002-6.605-94.797-6.605-142.054 0.077-13.722 1.946-23.398 14.387-21.862 28.211 1.843 16.896-0.614 34.202-7.168 49.997-21.504 52.25-81.408 77.21-133.632 55.706-15.821-6.502-29.85-16.947-40.602-30.157-8.653-10.752-24.32-12.698-35.379-4.301-37.99 28.851-71.68 62.694-100.045 100.762-8.090 10.701-6.451 25.83 3.635 34.637 33.28 28.902 44.288 75.597 27.546 116.301-16.742 40.653-57.498 66.048-101.427 63.155-13.363-0.845-25.19 8.755-26.982 21.99-6.63 47.002-6.63 94.822 0.102 142.080 1.946 13.696 14.387 23.322 28.16 21.811 16.896-1.818 34.202 0.691 50.022 7.168 52.224 21.53 77.21 81.459 55.706 133.734-6.502 15.795-16.947 29.773-30.157 40.525-10.803 8.73-12.698 24.346-4.352 35.354 28.877 38.042 62.822 71.731 100.813 100.122 1.741 1.357 3.661 2.355 5.606 3.2 9.933 4.045 21.709 1.536 29.082-6.938 28.826-33.178 75.571-44.262 116.275-27.52 40.653 16.742 66.048 57.498 63.13 101.453-0.819 13.338 8.755 25.165 22.067 27.059 47.002 6.579 94.72 6.554 142.029-0.102 13.645-1.971 23.347-14.464 21.811-28.237-1.843-16.947 0.691-34.253 7.194-50.048 21.504-52.25 81.459-77.21 133.658-55.68 15.795 6.528 29.85 16.947 40.55 30.157 8.704 10.803 24.346 12.698 35.405 4.326 37.99-28.902 71.654-62.746 100.096-100.813 7.987-10.675 6.4-25.805-3.712-34.662-33.254-28.826-44.288-75.571-27.546-116.224 16.742-40.73 57.498-66.099 101.453-63.232 13.338 0.922 25.139-8.678 27.008-21.965 6.554-47.002 6.502-94.771-0.128-142.003zM971.059 554.010c-56.141 5.274-105.702 41.114-127.642 94.464s-12.058 113.613 24.090 156.902c-17.69 21.478-37.453 41.318-58.854 59.315-12.749-11.213-27.392-20.352-43.238-26.854-78.259-32.282-168.243 5.197-200.499 83.584-6.502 15.718-10.291 32.563-11.29 49.536-27.853 2.56-55.859 2.637-83.61 0.077-5.274-56.090-41.114-105.677-94.464-127.616-53.35-21.99-113.613-11.981-156.928 24.064-21.504-17.69-41.318-37.453-59.29-58.88 11.213-12.723 20.352-27.392 26.906-43.136 32.205-78.387-5.274-168.294-83.584-200.55-15.821-6.502-32.589-10.342-49.613-11.366-2.534-27.853-2.586-55.859 0-83.558 56.090-5.299 105.626-41.088 127.565-94.438 21.965-53.402 12.058-113.638-24.090-156.902 17.69-21.555 37.478-41.395 58.88-59.341 12.749 11.213 27.392 20.352 43.213 26.854 78.285 32.256 168.218-5.248 200.474-83.558 6.528-15.795 10.342-32.589 11.366-49.613 27.853-2.509 55.808-2.56 83.558 0 5.299 56.090 41.139 105.6 94.49 127.59 53.35 21.939 113.638 12.006 156.902-24.090 21.504 17.741 41.293 37.453 59.29 58.854-11.213 12.8-20.352 27.392-26.854 43.213-32.256 78.31 5.248 168.294 83.507 200.499 15.846 6.502 32.691 10.342 49.638 11.392 2.56 27.853 2.611 55.808 0.077 83.558zM512 307.2c-113.101 0-204.8 91.699-204.8 204.8 0 113.126 91.699 204.826 204.8 204.826s204.8-91.699 204.8-204.826c0-113.101-91.699-204.8-204.8-204.8zM512 665.626c-84.813 0-153.6-68.813-153.6-153.626 0-84.838 68.787-153.6 153.6-153.6 84.838 0 153.6 68.762 153.6 153.6 0 84.813-68.762 153.626-153.6 153.626z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["settings"]},"attrs":[{}],"properties":{"order":22,"id":5,"name":"settings","prevSize":32,"code":59683},"setIdx":5,"setId":1,"iconIdx":62},{"icon":{"paths":["M77.005 102.605h128v332.8c0 14.131 11.418 25.6 25.6 25.6 14.106 0 25.6-11.469 25.6-25.6v-332.8h128c14.106 0 25.6-11.469 25.6-25.6 0-14.157-11.494-25.6-25.6-25.6h-307.2c-14.182 0-25.6 11.443-25.6 25.6 0 14.106 11.418 25.6 25.6 25.6zM947.405 716.979h-179.2v-102.4h179.2c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-204.8c-14.182 0-25.6 11.443-25.6 25.6v358.4c0 14.157 11.418 25.6 25.6 25.6 14.157 0 25.6-11.443 25.6-25.6v-179.2h179.2c14.157 0 25.6-11.443 25.6-25.6s-11.494-25.6-25.6-25.6zM965.094 58.47c-9.958-9.933-26.112-9.933-36.045 0l-870.605 870.579c-9.958 9.984-9.958 26.086 0 36.045 10.010 9.984 26.112 9.984 36.045 0l870.605-870.579c9.958-9.933 9.958-26.086 0-36.045z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["type-boolean"]},"attrs":[{}],"properties":{"order":21,"id":4,"name":"type-Boolean","prevSize":32,"code":59684},"setIdx":5,"setId":1,"iconIdx":63},{"icon":{"paths":["M947.2 102.4h-128v-25.6c0-14.131-11.469-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-512v-25.6c0-14.131-11.52-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-128c-42.342 0-76.8 34.458-76.8 76.8v716.8c0 42.342 34.458 76.8 76.8 76.8h870.4c42.342 0 76.8-34.458 76.8-76.8v-716.8c0-42.342-34.458-76.8-76.8-76.8zM972.8 896c0 14.131-11.469 25.6-25.6 25.6h-870.4c-14.080 0-25.6-11.469-25.6-25.6v-537.6h921.6v537.6zM972.8 307.2h-921.6v-128c0-14.080 11.52-25.6 25.6-25.6h128v76.8c0 14.080 11.52 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h512v76.8c0 14.080 11.469 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h128c14.131 0 25.6 11.52 25.6 25.6v128zM332.8 512h51.2c14.080 0 25.6-11.52 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.52-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.52-25.6 25.6s11.52 25.6 25.6 25.6zM640 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.52-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.52-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 614.4h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 614.4h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 716.8h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 716.8h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 819.2h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 819.2h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["type-datetime"]},"attrs":[{}],"properties":{"order":24,"id":3,"name":"type-DateTime","prevSize":32,"code":59685},"setIdx":5,"setId":1,"iconIdx":64},{"icon":{"paths":["M179.2 256c0-28.262 22.938-51.2 51.2-51.2h25.6c14.157 0 25.6-11.443 25.6-25.6 0-14.131-11.443-25.6-25.6-25.6h-25.6c-56.55 0-102.4 45.85-102.4 102.4v179.2c0 28.262-22.938 51.2-51.2 51.2h-25.6c-14.157 0-25.6 11.469-25.6 25.6 0 14.157 11.443 25.6 25.6 25.6h25.6c28.262 0 51.2 22.938 51.2 51.2v179.2c0 56.55 45.85 102.4 102.4 102.4h25.6c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-25.6c-28.262 0-51.2-22.938-51.2-51.2v-179.2c0-30.746-13.85-58.061-35.328-76.8 21.478-18.765 35.328-46.029 35.328-76.8v-179.2zM972.8 486.4h-25.6c-28.262 0-51.2-22.938-51.2-51.2v-179.2c0-56.55-45.85-102.4-102.4-102.4h-25.6c-14.157 0-25.6 11.469-25.6 25.6 0 14.157 11.443 25.6 25.6 25.6h25.6c28.262 0 51.2 22.938 51.2 51.2v179.2c0 30.771 13.85 58.035 35.328 76.8-21.478 18.739-35.328 46.054-35.328 76.8v179.2c0 28.262-22.938 51.2-51.2 51.2h-25.6c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h25.6c56.55 0 102.4-45.85 102.4-102.4v-179.2c0-28.262 22.938-51.2 51.2-51.2h25.6c14.157 0 25.6-11.443 25.6-25.6 0-14.131-11.443-25.6-25.6-25.6zM512 332.8c-14.157 0-25.6 11.469-25.6 25.6 0 14.157 11.443 25.6 25.6 25.6s25.6-11.443 25.6-25.6c0-14.131-11.443-25.6-25.6-25.6zM512 435.2c-14.157 0-25.6 11.469-25.6 25.6v204.8c0 14.157 11.443 25.6 25.6 25.6s25.6-11.443 25.6-25.6v-204.8c0-14.131-11.443-25.6-25.6-25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["json"]},"attrs":[{}],"properties":{"order":20,"id":14,"name":"type-Json, json","prevSize":32,"code":59674},"setIdx":5,"setId":1,"iconIdx":65},{"icon":{"paths":["M256 665.6h-76.8v-332.8c0-14.131-11.469-25.6-25.6-25.6h-76.8c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h51.2v307.2h-76.8c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h204.8c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6zM614.4 307.2h-204.8c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h179.2v128h-179.2c-14.131 0-25.6 11.469-25.6 25.6v179.2c0 14.131 11.469 25.6 25.6 25.6h204.8c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-179.2v-128h179.2c14.131 0 25.6-11.469 25.6-25.6v-179.2c0-14.131-11.469-25.6-25.6-25.6zM972.8 307.2h-204.8c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h179.2v128h-179.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h179.2v128h-179.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h204.8c14.131 0 25.6-11.469 25.6-25.6v-358.4c0-14.131-11.469-25.6-25.6-25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["type-number"]},"attrs":[{}],"properties":{"order":32,"id":2,"name":"type-Number","prevSize":32,"code":59686},"setIdx":5,"setId":1,"iconIdx":66},{"icon":{"paths":["M870.4 921.6h-716.8c-14.131 0-25.6 11.443-25.6 25.6s11.469 25.6 25.6 25.6h716.8c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6zM194.688 817.152c13.030 5.555 28.083-0.461 33.613-13.44l125.030-291.712h317.338l125.005 291.712c4.173 9.677 13.568 15.488 23.526 15.488 3.405 0 6.81-0.64 10.112-2.048 13.005-5.606 18.995-20.659 13.44-33.638l-131.61-306.944c-0.051-0.051-0.051-0.154-0.102-0.205l-175.488-409.6c-4.045-9.472-13.312-15.565-23.552-15.565s-19.507 6.093-23.552 15.514l-175.488 409.6c-0.051 0.051-0.051 0.154-0.102 0.205l-131.61 306.97c-5.53 13.005 0.461 28.058 13.44 33.664zM512 141.773l136.704 319.027h-273.408l136.704-319.027z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["type-string"]},"attrs":[{}],"properties":{"order":48,"id":1,"name":"type-String","prevSize":32,"code":59687},"setIdx":5,"setId":1,"iconIdx":67},{"icon":{"paths":["M955.221 848c0-0.109 10.752 0 0 0-52.751-161.392-240.461-224-443.178-224-202.269 0-389.979 63.392-443.066 224-11.2-0.109 0-1.232 0 0 0 61.936 49.615 112 110.654 112h664.823c61.151 0 110.766-50.064 110.766-112zM290.399 288c0 123.648 99.231 336 221.645 336s221.645-212.352 221.645-336c0-123.648-99.231-224-221.645-224s-221.645 100.352-221.645 224z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["user"]},"attrs":[{}],"properties":{"order":33,"id":0,"name":"user","prevSize":32,"code":59688},"setIdx":5,"setId":1,"iconIdx":68},{"icon":{"paths":["M159.073 665.6l-159.073-134.055 159.073-133.819 36.818 37.29-117.062 96.057 117.062 97.237z","M493.247 536.029q0 33.042-9.441 57.115-9.205 24.073-25.961 40.122-16.521 15.813-39.178 23.601-22.657 7.552-49.327 7.552-26.197 0-48.855-4.012-22.421-3.776-42.954-10.385v-323.338h57.587v78.356l-2.36 47.203q12.981-16.757 30.21-26.905 17.465-10.149 41.774-10.149 21.241 0 37.762 8.496t27.614 24.309q11.329 15.577 17.229 37.998 5.9 22.185 5.9 50.035zM432.828 538.389q0-19.825-2.832-33.75t-8.26-22.893q-5.192-8.969-12.981-12.981-7.552-4.248-17.465-4.248-14.633 0-28.086 11.801-13.217 11.801-28.086 32.098v104.79q6.844 2.596 16.757 4.248 10.149 1.652 20.533 1.652 13.689 0 24.781-5.664 11.329-5.664 19.117-16.049 8.024-10.385 12.273-25.253 4.248-15.105 4.248-33.75z","M700.682 513.608q0.472-13.453-1.416-22.893-1.652-9.441-5.664-15.577-3.776-6.136-9.441-8.968t-12.981-2.832q-12.745 0-26.433 10.621-13.453 10.385-29.738 34.458v151.756h-59.003v-239.789h52.159l2.124 34.93q5.9-9.205 13.217-16.521 7.552-7.316 16.521-12.509 9.205-5.428 20.297-8.26t24.309-2.832q18.173 0 32.098 6.372 14.161 6.136 23.601 18.409 9.677 12.273 14.161 30.918 4.72 18.409 4.012 42.718z","M864.927 397.725l159.073 133.819-159.073 134.055-36.582-37.29 116.826-96.293-116.826-97.001z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["control-Html"]},"attrs":[{},{},{},{}],"properties":{"order":116,"id":217,"name":"control-Html","prevSize":32,"code":59744},"setIdx":5,"setId":1,"iconIdx":0},{"icon":{"paths":["M251.429 100.58c-87.896 0-160 72.104-160 160v525.714c0 87.896 72.104 160 160 160h525.714c87.896 0 160-72.104 160-160v-525.714c0-87.896-72.104-160-160-160zM251.429 146.295h525.714c62.961 0 114.286 51.325 114.286 114.286v525.714c0 62.961-51.325 114.286-114.286 114.286h-525.714c-62.961 0-114.286-51.325-114.286-114.286v-525.714c0-62.961 51.325-114.286 114.286-114.286z","M251.429 306.295c-0.096-0.001-0.21-0.002-0.323-0.002-12.625 0-22.859 10.235-22.859 22.859s10.235 22.859 22.859 22.859c0.114 0 0.227-0.001 0.34-0.002l-0.017 0h525.714c0.096 0.001 0.21 0.002 0.323 0.002 12.625 0 22.859-10.235 22.859-22.859s-10.235-22.859-22.859-22.859c-0.114 0-0.227 0.001-0.34 0.002l0.017-0z","M251.429 443.438c-0.096-0.001-0.21-0.002-0.323-0.002-12.625 0-22.859 10.235-22.859 22.859s10.235 22.859 22.859 22.859c0.114 0 0.227-0.001 0.34-0.002l-0.017 0h297.143c0.096 0.001 0.21 0.002 0.323 0.002 12.625 0 22.859-10.235 22.859-22.859s-10.235-22.859-22.859-22.859c-0.114 0-0.227 0.001-0.34 0.002l0.017-0z","M251.429 580.58c-0.096-0.001-0.21-0.002-0.323-0.002-12.625 0-22.859 10.235-22.859 22.859s10.235 22.859 22.859 22.859c0.114 0 0.227-0.001 0.34-0.002l-0.017 0h297.143c0.096 0.001 0.21 0.002 0.323 0.002 12.625 0 22.859-10.235 22.859-22.859s-10.235-22.859-22.859-22.859c-0.114 0-0.227 0.001-0.34 0.002l0.017-0z"],"attrs":[{},{},{},{}],"width":1029,"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["single-content"]},"attrs":[{},{},{},{}],"properties":{"order":112,"id":214,"name":"single-content","prevSize":32,"code":59736},"setIdx":5,"setId":1,"iconIdx":69},{"icon":{"paths":["M777.143 946.286h-525.714c-89.143 0-160-70.857-160-160v-297.143c0-89.143 70.857-160 160-160h525.714c89.143 0 160 70.857 160 160v297.143c0 89.143-70.857 160-160 160zM251.429 374.857c-64 0-114.286 50.286-114.286 114.286v297.143c0 64 50.286 114.286 114.286 114.286h525.714c64 0 114.286-50.286 114.286-114.286v-297.143c0-64-50.286-114.286-114.286-114.286h-525.714z","M731.429 580.571h-457.143c-13.714 0-22.857-9.143-22.857-22.857s9.143-22.857 22.857-22.857h457.143c13.714 0 22.857 9.143 22.857 22.857s-9.143 22.857-22.857 22.857z","M502.857 740.571h-228.571c-13.714 0-22.857-9.143-22.857-22.857s9.143-22.857 22.857-22.857h228.571c13.714 0 22.857 9.143 22.857 22.857s-9.143 22.857-22.857 22.857z","M777.143 260.571h-525.714c-13.714 0-22.857-9.143-22.857-22.857s9.143-22.857 22.857-22.857h525.714c13.714 0 22.857 9.143 22.857 22.857s-9.143 22.857-22.857 22.857z","M685.714 146.286h-342.857c-13.714 0-22.857-9.143-22.857-22.857s9.143-22.857 22.857-22.857h342.857c13.714 0 22.857 9.143 22.857 22.857s-9.143 22.857-22.857 22.857z"],"attrs":[{},{},{},{},{}],"width":1029,"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["multiple-content"]},"attrs":[{},{},{},{},{}],"properties":{"order":113,"id":213,"name":"multiple-content","prevSize":32,"code":59735},"setIdx":5,"setId":1,"iconIdx":70},{"icon":{"paths":["M832 268.8h-657.92c-15.36 0-25.6-10.24-25.6-25.6s10.24-25.6 25.6-25.6h657.92c15.36 0 25.6 10.24 25.6 25.6s-10.24 25.6-25.6 25.6z","M832 453.12h-409.6c-15.36 0-25.6-10.24-25.6-25.6s10.24-25.6 25.6-25.6h409.6c15.36 0 25.6 10.24 25.6 25.6s-10.24 25.6-25.6 25.6z","M832 642.56h-409.6c-15.36 0-25.6-10.24-25.6-25.6s10.24-25.6 25.6-25.6h409.6c15.36 0 25.6 10.24 25.6 25.6s-10.24 25.6-25.6 25.6z","M832 832h-409.6c-15.36 0-25.6-10.24-25.6-25.6s10.24-25.6 25.6-25.6h409.6c15.36 0 25.6 10.24 25.6 25.6s-10.24 25.6-25.6 25.6z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["type-Array"]},"attrs":[{},{},{},{}],"properties":{"order":108,"id":211,"name":"type-Array","prevSize":32,"code":59734},"setIdx":5,"setId":1,"iconIdx":71},{"icon":{"paths":["M292.571 713.143v128c0 20-16.571 36.571-36.571 36.571h-146.286c-20 0-36.571-16.571-36.571-36.571v-128c0-20 16.571-36.571 36.571-36.571h146.286c20 0 36.571 16.571 36.571 36.571zM309.714 109.714l-16 438.857c-0.571 20-17.714 36.571-37.714 36.571h-146.286c-20 0-37.143-16.571-37.714-36.571l-16-438.857c-0.571-20 15.429-36.571 35.429-36.571h182.857c20 0 36 16.571 35.429 36.571z"],"attrs":[{}],"width":366,"isMulticolor":false,"isMulticolor2":false,"tags":["exclamation"],"grid":14},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59733,"name":"exclamation"},"setIdx":5,"setId":1,"iconIdx":72},{"icon":{"paths":["M512 26.38l-424.96 242.8v485.64l424.96 242.8 424.96-242.8v-485.64l-424.96-242.8zM512 235.52l245.76 138.24v276.48l-245.76 138.24-245.76-138.24v-276.48l245.76-138.24z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["orleans"]},"attrs":[{}],"properties":{"order":99,"id":209,"name":"orleans","prevSize":32,"code":59723},"setIdx":5,"setId":1,"iconIdx":75},{"icon":{"paths":["M358.4 102.4c-28.314 0-51.2 22.886-51.2 51.2v204.8h51.2v-204.8h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-307.2v51.2h307.2c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-292.2zM716.8 189.8l117.4 117.4h-117.4z","M153.6 640v281.6h358.4v-281.6zM179.2 640v-76.8c0-84.48 69.12-153.6 153.6-153.6s153.6 69.12 153.6 153.6v76.8h-51.2v-76.8c0-56.32-46.080-102.4-102.4-102.4s-102.4 46.080-102.4 102.4v76.8z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["document-lock"]},"attrs":[{},{}],"properties":{"order":97,"id":208,"name":"document-lock","prevSize":32,"code":59721},"setIdx":5,"setId":1,"iconIdx":76},{"icon":{"paths":["M358.4 102.4c-28.314 0-51.2 22.886-51.2 51.2v153.6h51.2v-153.6h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-358.4v51.2h358.4c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-292.2zM716.8 189.8l117.4 117.4h-117.4zM332.8 460.8l-230.4 256v51.2h102.4v153.6h256v-153.6h102.4v-51.2zM332.8 537.3l161.5 179.5h-84.7v153.6h-153.6v-153.6h-84.7z","M102.4 357.532h460.8v52.068h-460.8v-52.068z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["document-unpublish"]},"attrs":[{},{}],"properties":{"order":96,"id":207,"name":"document-unpublish","prevSize":32,"code":59711},"setIdx":5,"setId":1,"iconIdx":77},{"icon":{"paths":["M614.286 420.571c0 4.571-2.286 9.714-5.714 13.143l-266.286 266.286c-3.429 3.429-8.571 5.714-13.143 5.714s-9.714-2.286-13.143-5.714l-266.286-266.286c-3.429-3.429-5.714-8.571-5.714-13.143s2.286-9.714 5.714-13.143l28.571-28.571c3.429-3.429 8-5.714 13.143-5.714 4.571 0 9.714 2.286 13.143 5.714l224.571 224.571 224.571-224.571c3.429-3.429 8.571-5.714 13.143-5.714s9.714 2.286 13.143 5.714l28.571 28.571c3.429 3.429 5.714 8.571 5.714 13.143z"],"attrs":[{}],"width":658,"isMulticolor":false,"isMulticolor2":false,"tags":["angle-down"],"grid":14},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":32,"code":59648,"name":"angle-down"},"setIdx":5,"setId":1,"iconIdx":78},{"icon":{"paths":["M358.286 310.857c0 4.571-2.286 9.714-5.714 13.143l-224.571 224.571 224.571 224.571c3.429 3.429 5.714 8.571 5.714 13.143s-2.286 9.714-5.714 13.143l-28.571 28.571c-3.429 3.429-8.571 5.714-13.143 5.714s-9.714-2.286-13.143-5.714l-266.286-266.286c-3.429-3.429-5.714-8.571-5.714-13.143s2.286-9.714 5.714-13.143l266.286-266.286c3.429-3.429 8.571-5.714 13.143-5.714s9.714 2.286 13.143 5.714l28.571 28.571c3.429 3.429 5.714 8 5.714 13.143z"],"attrs":[{}],"width":384,"isMulticolor":false,"isMulticolor2":false,"tags":["angle-left"],"grid":14},"attrs":[{}],"properties":{"order":2,"id":0,"prevSize":32,"code":59649,"name":"angle-left"},"setIdx":5,"setId":1,"iconIdx":79},{"icon":{"paths":["M340 548.571c0 4.571-2.286 9.714-5.714 13.143l-266.286 266.286c-3.429 3.429-8.571 5.714-13.143 5.714s-9.714-2.286-13.143-5.714l-28.571-28.571c-3.429-3.429-5.714-8-5.714-13.143 0-4.571 2.286-9.714 5.714-13.143l224.571-224.571-224.571-224.571c-3.429-3.429-5.714-8.571-5.714-13.143s2.286-9.714 5.714-13.143l28.571-28.571c3.429-3.429 8.571-5.714 13.143-5.714s9.714 2.286 13.143 5.714l266.286 266.286c3.429 3.429 5.714 8.571 5.714 13.143z"],"attrs":[{}],"width":347,"isMulticolor":false,"isMulticolor2":false,"tags":["angle-right"],"grid":14},"attrs":[{}],"properties":{"order":67,"id":0,"prevSize":32,"code":59697,"name":"angle-right"},"setIdx":5,"setId":1,"iconIdx":80},{"icon":{"paths":["M614.286 676.571c0 4.571-2.286 9.714-5.714 13.143l-28.571 28.571c-3.429 3.429-8 5.714-13.143 5.714-4.571 0-9.714-2.286-13.143-5.714l-224.571-224.571-224.571 224.571c-3.429 3.429-8.571 5.714-13.143 5.714s-9.714-2.286-13.143-5.714l-28.571-28.571c-3.429-3.429-5.714-8.571-5.714-13.143s2.286-9.714 5.714-13.143l266.286-266.286c3.429-3.429 8.571-5.714 13.143-5.714s9.714 2.286 13.143 5.714l266.286 266.286c3.429 3.429 5.714 8.571 5.714 13.143z"],"attrs":[{}],"width":658,"isMulticolor":false,"isMulticolor2":false,"tags":["angle-up"],"grid":14},"attrs":[{}],"properties":{"order":2,"id":2,"prevSize":32,"code":59651,"name":"angle-up"},"setIdx":5,"setId":1,"iconIdx":81},{"icon":{"paths":["M592 393.6h-156.8c-57.6 0-105.6-48-105.6-105.6v-182.4c0-57.6 48-105.6 105.6-105.6h156.8c57.6 0 105.6 48 105.6 105.6v182.4c-3.2 57.6-48 105.6-105.6 105.6zM432 64c-22.4 0-41.6 19.2-41.6 41.6v182.4c0 22.4 19.2 41.6 41.6 41.6h156.8c22.4 0 41.6-19.2 41.6-41.6v-182.4c0-22.4-19.2-41.6-41.6-41.6h-156.8z","M195.2 1024c-105.6 0-195.2-89.6-195.2-195.2 0-108.8 89.6-195.2 195.2-195.2s195.2 89.6 195.2 195.2c3.2 105.6-86.4 195.2-195.2 195.2zM195.2 694.4c-73.6 0-131.2 60.8-131.2 131.2 0 73.6 60.8 134.4 131.2 134.4 73.6 0 131.2-60.8 131.2-131.2 3.2-73.6-57.6-134.4-131.2-134.4z","M828.8 1024c-108.8 0-195.2-89.6-195.2-195.2 0-108.8 89.6-195.2 195.2-195.2s195.2 89.6 195.2 195.2c0 105.6-89.6 195.2-195.2 195.2zM828.8 694.4c-73.6 0-131.2 60.8-131.2 131.2 0 73.6 60.8 131.2 131.2 131.2 73.6 0 131.2-60.8 131.2-131.2s-60.8-131.2-131.2-131.2z","M332.8 640c-6.4 0-12.8 0-16-3.2-16-9.6-19.2-28.8-9.6-44.8l83.2-137.6c9.6-16 28.8-19.2 44.8-9.6s19.2 28.8 9.6 44.8l-83.2 137.6c-6.4 6.4-16 12.8-28.8 12.8z","M691.2 640c-9.6 0-22.4-6.4-28.8-16l-83.2-137.6c-9.6-16-3.2-35.2 9.6-44.8s35.2-3.2 44.8 9.6l83.2 137.6c9.6 16 3.2 35.2-9.6 44.8-6.4 6.4-12.8 6.4-16 6.4z"],"attrs":[{},{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["api"]},"attrs":[{},{},{},{},{}],"properties":{"order":94,"id":206,"name":"api","prevSize":32,"code":59717},"setIdx":5,"setId":1,"iconIdx":82},{"icon":{"paths":["M800 1024h-576c-124.8 0-224-99.2-224-224v-576c0-124.8 99.2-224 224-224h576c124.8 0 224 99.2 224 224v576c0 124.8-99.2 224-224 224zM224 64c-89.6 0-160 70.4-160 160v576c0 89.6 70.4 160 160 160h576c89.6 0 160-70.4 160-160v-576c0-89.6-70.4-160-160-160h-576z","M771.2 860.8h-438.4c-12.8 0-22.4-6.4-28.8-19.2s-3.2-25.6 3.2-35.2l300.8-355.2c6.4-6.4 16-12.8 25.6-12.8s19.2 6.4 25.6 12.8l192 275.2c3.2 3.2 3.2 6.4 3.2 9.6 16 44.8 3.2 73.6-6.4 89.6-22.4 32-70.4 35.2-76.8 35.2zM403.2 796.8h371.2c6.4 0 22.4-3.2 25.6-9.6 3.2-3.2 3.2-12.8 0-25.6l-166.4-236.8-230.4 272z","M332.8 502.4c-76.8 0-140.8-64-140.8-140.8s64-140.8 140.8-140.8 140.8 64 140.8 140.8-60.8 140.8-140.8 140.8zM332.8 284.8c-41.6 0-76.8 32-76.8 76.8s35.2 76.8 76.8 76.8 76.8-35.2 76.8-76.8-32-76.8-76.8-76.8z"],"attrs":[{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["assets"]},"attrs":[{},{},{}],"properties":{"order":95,"id":205,"name":"assets","prevSize":32,"code":59720},"setIdx":5,"setId":1,"iconIdx":83},{"icon":{"paths":["M932.571 548.571c0 20-16.571 36.571-36.571 36.571h-128c0 71.429-15.429 125.143-38.286 165.714l118.857 119.429c14.286 14.286 14.286 37.143 0 51.429-6.857 7.429-16.571 10.857-25.714 10.857s-18.857-3.429-25.714-10.857l-113.143-112.571s-74.857 68.571-172 68.571v-512h-73.143v512c-103.429 0-178.857-75.429-178.857-75.429l-104.571 118.286c-7.429 8-17.143 12-27.429 12-8.571 0-17.143-2.857-24.571-9.143-14.857-13.714-16-36.571-2.857-52l115.429-129.714c-20-39.429-33.143-90.286-33.143-156.571h-128c-20 0-36.571-16.571-36.571-36.571s16.571-36.571 36.571-36.571h128v-168l-98.857-98.857c-14.286-14.286-14.286-37.143 0-51.429s37.143-14.286 51.429 0l98.857 98.857h482.286l98.857-98.857c14.286-14.286 37.143-14.286 51.429 0s14.286 37.143 0 51.429l-98.857 98.857v168h128c20 0 36.571 16.571 36.571 36.571zM658.286 219.429h-365.714c0-101.143 81.714-182.857 182.857-182.857s182.857 81.714 182.857 182.857z"],"attrs":[{}],"width":951,"isMulticolor":false,"isMulticolor2":false,"tags":["bug"],"grid":14},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59709,"name":"bug"},"setIdx":5,"setId":1,"iconIdx":84},{"icon":{"paths":["M585.143 402.286c0 9.714-4 18.857-10.857 25.714l-256 256c-6.857 6.857-16 10.857-25.714 10.857s-18.857-4-25.714-10.857l-256-256c-6.857-6.857-10.857-16-10.857-25.714 0-20 16.571-36.571 36.571-36.571h512c20 0 36.571 16.571 36.571 36.571z"],"attrs":[{}],"width":585,"isMulticolor":false,"isMulticolor2":false,"tags":["caret-down"],"grid":14},"attrs":[{}],"properties":{"order":4,"id":4,"prevSize":32,"code":59692,"name":"caret-down"},"setIdx":5,"setId":1,"iconIdx":85},{"icon":{"paths":["M365.714 256v512c0 20-16.571 36.571-36.571 36.571-9.714 0-18.857-4-25.714-10.857l-256-256c-6.857-6.857-10.857-16-10.857-25.714s4-18.857 10.857-25.714l256-256c6.857-6.857 16-10.857 25.714-10.857 20 0 36.571 16.571 36.571 36.571z"],"attrs":[{}],"width":402,"isMulticolor":false,"isMulticolor2":false,"tags":["caret-left"],"grid":14},"attrs":[{}],"properties":{"order":2,"id":6,"prevSize":32,"code":59690,"name":"caret-left"},"setIdx":5,"setId":1,"iconIdx":86},{"icon":{"paths":["M329.143 512c0 9.714-4 18.857-10.857 25.714l-256 256c-6.857 6.857-16 10.857-25.714 10.857-20 0-36.571-16.571-36.571-36.571v-512c0-20 16.571-36.571 36.571-36.571 9.714 0 18.857 4 25.714 10.857l256 256c6.857 6.857 10.857 16 10.857 25.714z"],"attrs":[{}],"width":329,"isMulticolor":false,"isMulticolor2":false,"tags":["caret-right"],"grid":14},"attrs":[{}],"properties":{"order":1,"id":7,"prevSize":32,"code":59689,"name":"caret-right"},"setIdx":5,"setId":1,"iconIdx":87},{"icon":{"paths":["M585.143 694.857c0 20-16.571 36.571-36.571 36.571h-512c-20 0-36.571-16.571-36.571-36.571 0-9.714 4-18.857 10.857-25.714l256-256c6.857-6.857 16-10.857 25.714-10.857s18.857 4 25.714 10.857l256 256c6.857 6.857 10.857 16 10.857 25.714z"],"attrs":[{}],"width":585,"isMulticolor":false,"isMulticolor2":false,"tags":["caret-up"],"grid":14},"attrs":[{}],"properties":{"order":3,"id":5,"prevSize":32,"code":59691,"name":"caret-up"},"setIdx":5,"setId":1,"iconIdx":88},{"icon":{"paths":["M800 1024h-576c-124.8 0-224-99.2-224-224v-576c0-124.8 99.2-224 224-224h576c124.8 0 224 99.2 224 224v576c0 124.8-99.2 224-224 224zM224 64c-89.6 0-160 70.4-160 160v576c0 89.6 70.4 160 160 160h576c89.6 0 160-70.4 160-160v-576c0-89.6-70.4-160-160-160h-576z","M480 448h-211.2c-57.6 0-105.6-48-105.6-105.6v-73.6c0-57.6 48-105.6 105.6-105.6h211.2c57.6 0 105.6 48 105.6 105.6v73.6c0 57.6-48 105.6-105.6 105.6zM268.8 227.2c-22.4 0-41.6 19.2-41.6 41.6v73.6c0 22.4 19.2 41.6 41.6 41.6h211.2c22.4 0 41.6-19.2 41.6-41.6v-73.6c0-22.4-19.2-41.6-41.6-41.6h-211.2z","M828.8 611.2h-633.6c-19.2 0-32-12.8-32-32s12.8-32 32-32h630.4c19.2 0 32 12.8 32 32s-12.8 32-28.8 32z","M553.6 777.6h-358.4c-19.2 0-32-12.8-32-32s12.8-32 32-32h355.2c19.2 0 32 12.8 32 32s-12.8 32-28.8 32z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["content"]},"attrs":[{},{},{},{}],"properties":{"order":93,"id":204,"name":"contents, trigger-ContentChanged","prevSize":32,"code":59718},"setIdx":5,"setId":1,"iconIdx":89},{"icon":{"paths":["M947.2 102.4h-128v-25.6c0-14.131-11.469-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-512v-25.6c0-14.131-11.52-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-128c-42.342 0-76.8 34.458-76.8 76.8v716.8c0 42.342 34.458 76.8 76.8 76.8h870.4c42.342 0 76.8-34.458 76.8-76.8v-716.8c0-42.342-34.458-76.8-76.8-76.8zM972.8 896c0 14.131-11.469 25.6-25.6 25.6h-870.4c-14.080 0-25.6-11.469-25.6-25.6v-537.6h921.6v537.6zM972.8 307.2h-921.6v-128c0-14.080 11.52-25.6 25.6-25.6h128v76.8c0 14.080 11.52 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h512v76.8c0 14.080 11.469 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h128c14.131 0 25.6 11.52 25.6 25.6v128zM332.8 512h51.2c14.080 0 25.6-11.52 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.52-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.52-25.6 25.6s11.52 25.6 25.6 25.6zM640 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.52-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.52-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 614.4h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 614.4h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 716.8h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 716.8h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 819.2h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 819.2h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["control-date"]},"attrs":[{}],"properties":{"order":71,"id":42,"name":"control-Date","prevSize":32,"code":59702},"setIdx":5,"setId":1,"iconIdx":90},{"icon":{"paths":["M486.4 409.6h51.2c14.080 0 25.6 11.52 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.52-25.6-25.6s11.52-25.6 25.6-25.6zM230.4 614.4c14.080 0 25.6 11.469 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.469-25.6-25.6s11.52-25.6 25.6-25.6h51.2zM230.4 512c14.080 0 25.6 11.469 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.469-25.6-25.6s11.52-25.6 25.6-25.6h51.2zM51.2 742.4v-435.2h665.6v102.4h51.2v-281.6c0-42.342-34.458-76.8-76.8-76.8h-128v-25.6c0-14.131-11.469-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-256v-25.6c0-14.131-11.52-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-128c-42.342 0-76.8 34.458-76.8 76.8v614.4c0 42.342 34.458 76.8 76.8 76.8h332.8v-51.2h-332.8c-14.080 0-25.6-11.469-25.6-25.6zM51.2 128c0-14.080 11.52-25.6 25.6-25.6h128v76.8c0 14.080 11.52 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h256v76.8c0 14.080 11.469 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h128c14.131 0 25.6 11.52 25.6 25.6v128h-665.6v-128zM384 409.6c14.080 0 25.6 11.52 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.52-25.6-25.6s11.52-25.6 25.6-25.6h51.2zM742.4 460.8c-155.546 0-281.6 126.054-281.6 281.6s126.054 281.6 281.6 281.6 281.6-126.054 281.6-281.6-126.054-281.6-281.6-281.6zM742.4 972.8c-127.232 0-230.4-103.168-230.4-230.4s103.168-230.4 230.4-230.4 230.4 103.168 230.4 230.4-103.168 230.4-230.4 230.4zM384 512c14.080 0 25.6 11.469 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.469-25.6-25.6s11.52-25.6 25.6-25.6h51.2zM384 614.4c14.080 0 25.6 11.469 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.469-25.6-25.6s11.52-25.6 25.6-25.6h51.2zM844.8 716.8c14.131 0 25.6 11.469 25.6 25.6s-11.469 25.6-25.6 25.6h-102.4c-14.131 0-25.6-11.469-25.6-25.6v-102.4c0-14.131 11.469-25.6 25.6-25.6s25.6 11.469 25.6 25.6v76.8h76.8z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["control-date-time"]},"attrs":[{}],"properties":{"order":70,"id":41,"name":"control-DateTime","prevSize":32,"code":59703},"setIdx":5,"setId":1,"iconIdx":91},{"icon":{"paths":["M793.6 609.416h-61.838v-28.108h-0.783q-21.135 33.092-62.034 33.092-37.573 0-60.469-26.912-22.896-27.112-22.896-75.554 0-50.635 25.244-81.136t66.144-30.501q38.747 0 54.011 28.308h0.783v-121.405h61.838v302.216zM732.936 510.139v-15.35q0-19.935-11.35-33.092t-29.549-13.157q-20.548 0-32.093 16.546-11.546 16.347-11.546 45.053 0 26.912 11.154 41.465t30.919 14.553q18.786 0 30.528-15.35 11.937-15.35 11.937-40.668zM548.594 609.416h-61.643v-116.421q0-44.455-32.093-44.455-15.264 0-24.853 13.357t-9.589 33.292v114.228h-61.839v-117.617q0-43.259-31.506-43.259-15.851 0-25.44 12.758-9.393 12.758-9.393 34.687v113.431h-61.838v-204.135h61.838v31.896h0.783q9.589-16.347 26.81-26.514 17.417-10.366 37.964-10.366 42.465 0 58.12 38.076 22.896-38.076 67.318-38.076 65.361 0 65.361 82.133v126.987zM0 0v204.8h76.8v76.8h51.2v-76.8h76.8v-204.8zM819.2 0v204.8h204.8v-204.8zM51.2 51.2h102.4v102.4h-102.4zM870.4 51.2h102.4v102.4h-102.4zM281.6 76.8v51.2h102.4v-51.2zM486.4 76.8v51.2h102.4v-51.2zM691.2 76.8v51.2h102.4v-51.2zM896 281.6v102.4h51.2v-102.4zM76.8 384v102.4h51.2v-102.4zM896 486.4v102.4h51.2v-102.4zM76.8 588.8v102.4h51.2v-102.4zM896 691.2v102.4h51.2v-102.4zM76.8 793.6v25.6h-76.8v204.8h204.8v-76.8h76.8v-51.2h-76.8v-76.8h-76.8v-25.6zM819.2 819.2v76.8h-25.6v51.2h25.6v76.8h204.8v-204.8zM51.2 870.4h102.4v102.4h-102.4zM870.4 870.4h102.4v102.4h-102.4zM384 896v51.2h102.4v-51.2zM588.8 896v51.2h102.4v-51.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["control-Markdown"]},"attrs":[{}],"properties":{"order":72,"id":43,"name":"control-Markdown","prevSize":32,"code":59704},"setIdx":5,"setId":1,"iconIdx":92},{"icon":{"paths":["M292.571 713.143v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM292.571 420.571v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM658.286 713.143v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM292.571 128v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM658.286 420.571v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM1024 713.143v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM658.286 128v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM1024 420.571v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM1024 128v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857z"],"width":1024,"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["th"],"defaultCode":61450,"grid":14},"attrs":[],"properties":{"name":"grid","id":14,"order":83,"prevSize":32,"code":61450},"setIdx":5,"setId":1,"iconIdx":93},{"icon":{"paths":["M877.714 768v73.143c0 20-16.571 36.571-36.571 36.571h-804.571c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h804.571c20 0 36.571 16.571 36.571 36.571zM877.714 475.429v73.143c0 20-16.571 36.571-36.571 36.571h-804.571c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h804.571c20 0 36.571 16.571 36.571 36.571zM877.714 182.857v73.143c0 20-16.571 36.571-36.571 36.571h-804.571c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h804.571c20 0 36.571 16.571 36.571 36.571z"],"width":877.7142857142857,"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["bars","navicon","reorder"],"defaultCode":61641,"grid":14},"attrs":[],"properties":{"name":"list1","id":178,"order":89,"prevSize":32,"code":61641},"setIdx":5,"setId":1,"iconIdx":94},{"icon":{"paths":["M512 64c-131.696 0-239.125 107.4-239.125 239 0 65.8 24.831 146.717 65.375 215.25 19.653 33.221 43.902 63.853 71.75 87.125-59.423 7.524-122.009 9.415-172.125 32-79.809 35.967-144.343 94.74-172.375 178.625-1.5 9.499 0 0-1.5 9v0.499c0 73.995 60.563 134.501 134.375 134.501h627.125c73.888 0 134.5-60.506 134.5-134.5l-1.5-9.375c-27.845-84.263-92.273-143.119-172.125-179-50.17-22.544-112.844-24.421-172.375-31.875 27.792-23.26 52.002-53.831 71.625-87 40.544-68.533 65.375-149.45 65.375-215.25 0-131.6-107.304-239-239-239zM512 124c99.241 0 179 79.875 179 179 0 49.562-21.877 125.381-57 184.75s-81.435 98.75-122 98.75c-40.565 0-86.877-39.381-122-98.75s-57.125-135.188-57.125-184.75c0-99.125 79.884-179 179.125-179zM512 646.5c92.551 0 180.829 14.406 249.75 45.375 66.784 30.009 113.649 74.724 136.5 137.75-2.447 39.259-32.9 70.375-72.75 70.375h-627.125c-39.678 0-70.116-31.051-72.625-70.25 22.978-62.705 69.953-107.523 136.75-137.625 68.937-31.067 157.205-45.625 249.5-45.625z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["user-o"]},"attrs":[{}],"properties":{"order":64,"id":38,"name":"user-o","prevSize":32,"code":59698},"setIdx":5,"setId":1,"iconIdx":95},{"icon":{"paths":["M217.6 992c-3.2 0-3.2 0-6.4 0h-3.2c-144-25.6-208-144-208-249.6 0-99.2 57.6-208 185.6-240v-147.2c0-19.2 12.8-32 32-32s32 12.8 32 32v172.8c0 16-12.8 28.8-25.6 32-108.8 16-160 102.4-160 182.4s48 166.4 153.6 185.6h6.4c16 3.2 28.8 19.2 25.6 38.4-3.2 16-16 25.6-32 25.6z","M774.4 1001.6c0 0 0 0 0 0-102.4 0-211.2-60.8-243.2-185.6h-176c-19.2 0-32-12.8-32-32s12.8-32 32-32h201.6c16 0 28.8 12.8 32 25.6 16 108.8 102.4 156.8 182.4 160 80 0 166.4-48 185.6-153.6v-3.2c3.2-16 19.2-28.8 38.4-25.6 16 3.2 28.8 19.2 25.6 38.4v3.2c-22.4 140.8-140.8 204.8-246.4 204.8z","M787.2 678.4c-19.2 0-32-12.8-32-32v-176c0-16 12.8-28.8 25.6-32 108.8-16 156.8-102.4 160-182.4 0-80-48-166.4-153.6-185.6h-3.2c-19.2-6.4-32-22.4-28.8-38.4s19.2-28.8 38.4-25.6h3.2c144 25.6 208 144 208 249.6 0 99.2-60.8 208-185.6 240v150.4c0 16-16 32-32 32z","M41.6 246.4c-3.2 0-3.2 0-6.4 0-16-3.2-28.8-19.2-25.6-35.2v-3.2c25.6-144 140.8-208 246.4-208 0 0 3.2 0 3.2 0 99.2 0 208 60.8 240 185.6h147.2c19.2 0 32 12.8 32 32s-12.8 32-32 32h-172.8c-16 0-28.8-12.8-32-25.6-16-108.8-102.4-156.8-182.4-160-80 0-166.4 48-185.6 153.6v3.2c-3.2 16-16 25.6-32 25.6z","M256 387.2c-32 0-67.2-12.8-92.8-38.4-51.2-51.2-51.2-134.4 0-185.6 25.6-22.4 57.6-35.2 92.8-35.2s67.2 12.8 92.8 38.4c25.6 25.6 38.4 57.6 38.4 92.8s-12.8 67.2-38.4 92.8c-25.6 22.4-57.6 35.2-92.8 35.2zM256 192c-16 0-32 6.4-44.8 19.2-25.6 25.6-25.6 67.2 0 92.8s67.2 25.6 92.8 0c12.8-12.8 19.2-28.8 19.2-48s-6.4-32-19.2-44.8-28.8-19.2-48-19.2z","M771.2 873.6c-32 0-67.2-12.8-92.8-38.4-51.2-51.2-51.2-134.4 0-185.6 25.6-25.6 57.6-38.4 92.8-38.4s67.2 12.8 92.8 38.4c25.6 25.6 38.4 57.6 38.4 92.8s-12.8 67.2-38.4 92.8c-28.8 25.6-60.8 38.4-92.8 38.4zM771.2 678.4c-19.2 0-35.2 6.4-48 19.2-25.6 25.6-25.6 67.2 0 92.8s67.2 25.6 92.8 0c12.8-12.8 19.2-28.8 19.2-48s-6.4-35.2-19.2-48-28.8-16-44.8-16z","M745.6 387.2c-32 0-67.2-12.8-92.8-38.4s-38.4-57.6-38.4-92.8 12.8-67.2 38.4-92.8c25.6-22.4 60.8-35.2 92.8-35.2s67.2 12.8 92.8 38.4c51.2 51.2 51.2 134.4 0 185.6v0c-25.6 22.4-57.6 35.2-92.8 35.2zM745.6 192c-19.2 0-35.2 6.4-48 19.2s-19.2 28.8-19.2 48 6.4 35.2 19.2 48c25.6 25.6 67.2 25.6 92.8 0s25.6-67.2 0-92.8c-9.6-16-25.6-22.4-44.8-22.4z","M259.2 873.6c-32 0-67.2-12.8-92.8-38.4s-38.4-57.6-38.4-92.8 12.8-67.2 38.4-92.8c25.6-22.4 57.6-35.2 92.8-35.2s67.2 12.8 92.8 38.4c51.2 51.2 51.2 134.4 0 185.6v0c-25.6 22.4-57.6 35.2-92.8 35.2zM259.2 678.4c-19.2 0-35.2 6.4-48 19.2s-19.2 28.8-19.2 48 6.4 35.2 19.2 48c25.6 25.6 67.2 25.6 92.8 0s25.6-67.2 0-92.8c-9.6-16-25.6-22.4-44.8-22.4z"],"attrs":[{},{},{},{},{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["webhooks"]},"attrs":[{},{},{},{},{},{},{},{}],"properties":{"order":92,"id":203,"name":"rules","prevSize":32,"code":59719},"setIdx":5,"setId":1,"iconIdx":96}],"height":1024,"metadata":{"name":"icomoon"},"preferences":{"showGlyphs":true,"showCodes":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"icomoon"},"metrics":{"emSize":1024,"baseline":6.25,"whitespace":50},"embed":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215,"classSelector":".icon","name":"icomoon"},"historySize":100,"gridSize":16}} \ No newline at end of file +{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M950.857 932.571v-621.714c0-9.714-8.571-18.286-18.286-18.286h-621.714c-9.714 0-18.286 8.571-18.286 18.286v621.714c0 9.714 8.571 18.286 18.286 18.286h621.714c9.714 0 18.286-8.571 18.286-18.286zM1024 310.857v621.714c0 50.286-41.143 91.429-91.429 91.429h-621.714c-50.286 0-91.429-41.143-91.429-91.429v-621.714c0-50.286 41.143-91.429 91.429-91.429h621.714c50.286 0 91.429 41.143 91.429 91.429zM804.571 91.429v91.429h-73.143v-91.429c0-9.714-8.571-18.286-18.286-18.286h-621.714c-9.714 0-18.286 8.571-18.286 18.286v621.714c0 9.714 8.571 18.286 18.286 18.286h91.429v73.143h-91.429c-50.286 0-91.429-41.143-91.429-91.429v-621.714c0-50.286 41.143-91.429 91.429-91.429h621.714c50.286 0 91.429 41.143 91.429 91.429z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["clone"],"grid":14},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":28,"code":59754,"name":"clone"},"setIdx":0,"setId":3,"iconIdx":0},{"icon":{"paths":["M498.787 330.323v-49.548h112.31v-66.065c0-49.548-39.639-89.187-89.187-89.187s-89.187 39.639-89.187 89.187v541.729l89.187 161.858 89.187-161.858v-426.116z","M360.052 716.8h-66.065c-59.458 0-105.703-46.245-105.703-105.703v-254.348c0-59.458 46.245-105.703 105.703-105.703h66.065v-42.942h-66.065c-82.581 0-148.645 66.065-148.645 148.645v254.348c0 82.581 66.065 148.645 148.645 148.645h66.065z","M852.232 260.955c-26.426-33.032-66.065-52.852-109.006-52.852h-59.458v42.942h39.639c42.942 0 82.581 19.819 109.006 52.852l145.342 181.677-142.039 178.374c-26.426 33.032-69.368 52.852-112.31 52.852h-36.335v42.942h56.155c42.942 0 85.884-19.819 112.31-52.852l178.374-221.316z"],"attrs":[{},{},{}],"width":1140,"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["control-Tags"]},"attrs":[{},{},{}],"properties":{"order":119,"id":220,"name":"control-Tags","prevSize":28,"code":59747},"setIdx":2,"setId":1,"iconIdx":80},{"icon":{"paths":["M384 179.2h614.4c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-614.4c-14.131 0-25.6 11.443-25.6 25.6s11.469 25.6 25.6 25.6zM998.4 486.4h-614.4c-14.131 0-25.6 11.443-25.6 25.6s11.469 25.6 25.6 25.6h614.4c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6zM998.4 844.8h-614.4c-38.406 15.539-22.811 37.543 0 51.2h614.4c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6z","M0 0v307.2h307.2v-307.2zM47.4 47.4h212.4v212.4h-212.4z","M0 716.8v307.2h307.2v-307.2zM47.4 764.2h212.4v212.4h-212.4z","M0 358.4v307.2h307.2v-307.2zM47.4 405.8h212.4v212.4h-212.4z","M89.6 89.6h128v128h-128v-128z"],"attrs":[{},{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["control-Checkboxes"]},"attrs":[{},{},{},{},{}],"properties":{"order":118,"id":219,"name":"control-Checkboxes","prevSize":28,"code":59746},"setIdx":2,"setId":1,"iconIdx":81},{"icon":{"paths":["M159.073 665.6l-159.073-134.055 159.073-133.819 36.818 37.29-117.062 96.057 117.062 97.237z","M493.247 536.029q0 33.042-9.441 57.115-9.205 24.073-25.961 40.122-16.521 15.813-39.178 23.601-22.657 7.552-49.327 7.552-26.197 0-48.855-4.012-22.421-3.776-42.954-10.385v-323.338h57.587v78.356l-2.36 47.203q12.981-16.757 30.21-26.905 17.465-10.149 41.774-10.149 21.241 0 37.762 8.496t27.614 24.309q11.329 15.577 17.229 37.998 5.9 22.185 5.9 50.035zM432.828 538.389q0-19.825-2.832-33.75t-8.26-22.893q-5.192-8.969-12.981-12.981-7.552-4.248-17.465-4.248-14.633 0-28.086 11.801-13.217 11.801-28.086 32.098v104.79q6.844 2.596 16.757 4.248 10.149 1.652 20.533 1.652 13.689 0 24.781-5.664 11.329-5.664 19.117-16.049 8.024-10.385 12.273-25.253 4.248-15.105 4.248-33.75z","M700.682 513.608q0.472-13.453-1.416-22.893-1.652-9.441-5.664-15.577-3.776-6.136-9.441-8.968t-12.981-2.832q-12.745 0-26.433 10.621-13.453 10.385-29.738 34.458v151.756h-59.003v-239.789h52.159l2.124 34.93q5.9-9.205 13.217-16.521 7.552-7.316 16.521-12.509 9.205-5.428 20.297-8.26t24.309-2.832q18.173 0 32.098 6.372 14.161 6.136 23.601 18.409 9.677 12.273 14.161 30.918 4.72 18.409 4.012 42.718z","M864.927 397.725l159.073 133.819-159.073 134.055-36.582-37.29 116.826-96.293-116.826-97.001z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["control-Html"]},"attrs":[{},{},{},{}],"properties":{"order":116,"id":217,"name":"control-Html","prevSize":28,"code":59744},"setIdx":2,"setId":1,"iconIdx":82},{"icon":{"paths":["M251.429 100.58c-87.896 0-160 72.104-160 160v525.714c0 87.896 72.104 160 160 160h525.714c87.896 0 160-72.104 160-160v-525.714c0-87.896-72.104-160-160-160zM251.429 146.295h525.714c62.961 0 114.286 51.325 114.286 114.286v525.714c0 62.961-51.325 114.286-114.286 114.286h-525.714c-62.961 0-114.286-51.325-114.286-114.286v-525.714c0-62.961 51.325-114.286 114.286-114.286z","M251.429 306.295c-0.096-0.001-0.21-0.002-0.323-0.002-12.625 0-22.859 10.235-22.859 22.859s10.235 22.859 22.859 22.859c0.114 0 0.227-0.001 0.34-0.002l-0.017 0h525.714c0.096 0.001 0.21 0.002 0.323 0.002 12.625 0 22.859-10.235 22.859-22.859s-10.235-22.859-22.859-22.859c-0.114 0-0.227 0.001-0.34 0.002l0.017-0z","M251.429 443.438c-0.096-0.001-0.21-0.002-0.323-0.002-12.625 0-22.859 10.235-22.859 22.859s10.235 22.859 22.859 22.859c0.114 0 0.227-0.001 0.34-0.002l-0.017 0h297.143c0.096 0.001 0.21 0.002 0.323 0.002 12.625 0 22.859-10.235 22.859-22.859s-10.235-22.859-22.859-22.859c-0.114 0-0.227 0.001-0.34 0.002l0.017-0z","M251.429 580.58c-0.096-0.001-0.21-0.002-0.323-0.002-12.625 0-22.859 10.235-22.859 22.859s10.235 22.859 22.859 22.859c0.114 0 0.227-0.001 0.34-0.002l-0.017 0h297.143c0.096 0.001 0.21 0.002 0.323 0.002 12.625 0 22.859-10.235 22.859-22.859s-10.235-22.859-22.859-22.859c-0.114 0-0.227 0.001-0.34 0.002l0.017-0z"],"attrs":[{},{},{},{}],"width":1029,"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["single-content"]},"attrs":[{},{},{},{}],"properties":{"order":112,"id":214,"name":"single-content","prevSize":28,"code":59736},"setIdx":2,"setId":1,"iconIdx":83},{"icon":{"paths":["M777.143 946.286h-525.714c-89.143 0-160-70.857-160-160v-297.143c0-89.143 70.857-160 160-160h525.714c89.143 0 160 70.857 160 160v297.143c0 89.143-70.857 160-160 160zM251.429 374.857c-64 0-114.286 50.286-114.286 114.286v297.143c0 64 50.286 114.286 114.286 114.286h525.714c64 0 114.286-50.286 114.286-114.286v-297.143c0-64-50.286-114.286-114.286-114.286h-525.714z","M731.429 580.571h-457.143c-13.714 0-22.857-9.143-22.857-22.857s9.143-22.857 22.857-22.857h457.143c13.714 0 22.857 9.143 22.857 22.857s-9.143 22.857-22.857 22.857z","M502.857 740.571h-228.571c-13.714 0-22.857-9.143-22.857-22.857s9.143-22.857 22.857-22.857h228.571c13.714 0 22.857 9.143 22.857 22.857s-9.143 22.857-22.857 22.857z","M777.143 260.571h-525.714c-13.714 0-22.857-9.143-22.857-22.857s9.143-22.857 22.857-22.857h525.714c13.714 0 22.857 9.143 22.857 22.857s-9.143 22.857-22.857 22.857z","M685.714 146.286h-342.857c-13.714 0-22.857-9.143-22.857-22.857s9.143-22.857 22.857-22.857h342.857c13.714 0 22.857 9.143 22.857 22.857s-9.143 22.857-22.857 22.857z"],"attrs":[{},{},{},{},{}],"width":1029,"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["multiple-content"]},"attrs":[{},{},{},{},{}],"properties":{"order":113,"id":213,"name":"multiple-content","prevSize":28,"code":59735},"setIdx":2,"setId":1,"iconIdx":84},{"icon":{"paths":["M832 268.8h-657.92c-15.36 0-25.6-10.24-25.6-25.6s10.24-25.6 25.6-25.6h657.92c15.36 0 25.6 10.24 25.6 25.6s-10.24 25.6-25.6 25.6z","M832 453.12h-409.6c-15.36 0-25.6-10.24-25.6-25.6s10.24-25.6 25.6-25.6h409.6c15.36 0 25.6 10.24 25.6 25.6s-10.24 25.6-25.6 25.6z","M832 642.56h-409.6c-15.36 0-25.6-10.24-25.6-25.6s10.24-25.6 25.6-25.6h409.6c15.36 0 25.6 10.24 25.6 25.6s-10.24 25.6-25.6 25.6z","M832 832h-409.6c-15.36 0-25.6-10.24-25.6-25.6s10.24-25.6 25.6-25.6h409.6c15.36 0 25.6 10.24 25.6 25.6s-10.24 25.6-25.6 25.6z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["type-Array"]},"attrs":[{},{},{},{}],"properties":{"order":108,"id":211,"name":"type-Array","prevSize":28,"code":59734},"setIdx":2,"setId":1,"iconIdx":85},{"icon":{"paths":["M292.571 713.143v128c0 20-16.571 36.571-36.571 36.571h-146.286c-20 0-36.571-16.571-36.571-36.571v-128c0-20 16.571-36.571 36.571-36.571h146.286c20 0 36.571 16.571 36.571 36.571zM309.714 109.714l-16 438.857c-0.571 20-17.714 36.571-37.714 36.571h-146.286c-20 0-37.143-16.571-37.714-36.571l-16-438.857c-0.571-20 15.429-36.571 35.429-36.571h182.857c20 0 36 16.571 35.429 36.571z"],"attrs":[{}],"width":366,"isMulticolor":false,"isMulticolor2":false,"tags":["exclamation"],"grid":14},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":28,"code":59733,"name":"exclamation"},"setIdx":2,"setId":1,"iconIdx":86},{"icon":{"paths":["M512 26.38l-424.96 242.8v485.64l424.96 242.8 424.96-242.8v-485.64l-424.96-242.8zM512 235.52l245.76 138.24v276.48l-245.76 138.24-245.76-138.24v-276.48l245.76-138.24z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["orleans"]},"attrs":[{}],"properties":{"order":99,"id":209,"name":"orleans","prevSize":28,"code":59723},"setIdx":2,"setId":1,"iconIdx":87},{"icon":{"paths":["M358.4 102.4c-28.314 0-51.2 22.886-51.2 51.2v204.8h51.2v-204.8h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-307.2v51.2h307.2c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-292.2zM716.8 189.8l117.4 117.4h-117.4z","M153.6 640v281.6h358.4v-281.6zM179.2 640v-76.8c0-84.48 69.12-153.6 153.6-153.6s153.6 69.12 153.6 153.6v76.8h-51.2v-76.8c0-56.32-46.080-102.4-102.4-102.4s-102.4 46.080-102.4 102.4v76.8z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["document-lock"]},"attrs":[{},{}],"properties":{"order":97,"id":208,"name":"document-lock","prevSize":28,"code":59721},"setIdx":2,"setId":1,"iconIdx":88},{"icon":{"paths":["M358.4 102.4c-28.314 0-51.2 22.886-51.2 51.2v153.6h51.2v-153.6h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-358.4v51.2h358.4c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-292.2zM716.8 189.8l117.4 117.4h-117.4zM332.8 460.8l-230.4 256v51.2h102.4v153.6h256v-153.6h102.4v-51.2zM332.8 537.3l161.5 179.5h-84.7v153.6h-153.6v-153.6h-84.7z","M102.4 357.532h460.8v52.068h-460.8v-52.068z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["document-unpublish"]},"attrs":[{},{}],"properties":{"order":96,"id":207,"name":"document-unpublish","prevSize":28,"code":59711},"setIdx":2,"setId":1,"iconIdx":89},{"icon":{"paths":["M614.286 420.571c0 4.571-2.286 9.714-5.714 13.143l-266.286 266.286c-3.429 3.429-8.571 5.714-13.143 5.714s-9.714-2.286-13.143-5.714l-266.286-266.286c-3.429-3.429-5.714-8.571-5.714-13.143s2.286-9.714 5.714-13.143l28.571-28.571c3.429-3.429 8-5.714 13.143-5.714 4.571 0 9.714 2.286 13.143 5.714l224.571 224.571 224.571-224.571c3.429-3.429 8.571-5.714 13.143-5.714s9.714 2.286 13.143 5.714l28.571 28.571c3.429 3.429 5.714 8.571 5.714 13.143z"],"attrs":[{}],"width":658,"isMulticolor":false,"isMulticolor2":false,"tags":["angle-down"],"grid":14},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":28,"code":59648,"name":"angle-down"},"setIdx":2,"setId":1,"iconIdx":90},{"icon":{"paths":["M358.286 310.857c0 4.571-2.286 9.714-5.714 13.143l-224.571 224.571 224.571 224.571c3.429 3.429 5.714 8.571 5.714 13.143s-2.286 9.714-5.714 13.143l-28.571 28.571c-3.429 3.429-8.571 5.714-13.143 5.714s-9.714-2.286-13.143-5.714l-266.286-266.286c-3.429-3.429-5.714-8.571-5.714-13.143s2.286-9.714 5.714-13.143l266.286-266.286c3.429-3.429 8.571-5.714 13.143-5.714s9.714 2.286 13.143 5.714l28.571 28.571c3.429 3.429 5.714 8 5.714 13.143z"],"attrs":[{}],"width":384,"isMulticolor":false,"isMulticolor2":false,"tags":["angle-left"],"grid":14},"attrs":[{}],"properties":{"order":2,"id":0,"prevSize":28,"code":59649,"name":"angle-left"},"setIdx":2,"setId":1,"iconIdx":91},{"icon":{"paths":["M340 548.571c0 4.571-2.286 9.714-5.714 13.143l-266.286 266.286c-3.429 3.429-8.571 5.714-13.143 5.714s-9.714-2.286-13.143-5.714l-28.571-28.571c-3.429-3.429-5.714-8-5.714-13.143 0-4.571 2.286-9.714 5.714-13.143l224.571-224.571-224.571-224.571c-3.429-3.429-5.714-8.571-5.714-13.143s2.286-9.714 5.714-13.143l28.571-28.571c3.429-3.429 8.571-5.714 13.143-5.714s9.714 2.286 13.143 5.714l266.286 266.286c3.429 3.429 5.714 8.571 5.714 13.143z"],"attrs":[{}],"width":347,"isMulticolor":false,"isMulticolor2":false,"tags":["angle-right"],"grid":14},"attrs":[{}],"properties":{"order":67,"id":0,"prevSize":28,"code":59697,"name":"angle-right"},"setIdx":2,"setId":1,"iconIdx":92},{"icon":{"paths":["M614.286 676.571c0 4.571-2.286 9.714-5.714 13.143l-28.571 28.571c-3.429 3.429-8 5.714-13.143 5.714-4.571 0-9.714-2.286-13.143-5.714l-224.571-224.571-224.571 224.571c-3.429 3.429-8.571 5.714-13.143 5.714s-9.714-2.286-13.143-5.714l-28.571-28.571c-3.429-3.429-5.714-8.571-5.714-13.143s2.286-9.714 5.714-13.143l266.286-266.286c3.429-3.429 8.571-5.714 13.143-5.714s9.714 2.286 13.143 5.714l266.286 266.286c3.429 3.429 5.714 8.571 5.714 13.143z"],"attrs":[{}],"width":658,"isMulticolor":false,"isMulticolor2":false,"tags":["angle-up"],"grid":14},"attrs":[{}],"properties":{"order":2,"id":2,"prevSize":28,"code":59651,"name":"angle-up"},"setIdx":2,"setId":1,"iconIdx":93},{"icon":{"paths":["M592 393.6h-156.8c-57.6 0-105.6-48-105.6-105.6v-182.4c0-57.6 48-105.6 105.6-105.6h156.8c57.6 0 105.6 48 105.6 105.6v182.4c-3.2 57.6-48 105.6-105.6 105.6zM432 64c-22.4 0-41.6 19.2-41.6 41.6v182.4c0 22.4 19.2 41.6 41.6 41.6h156.8c22.4 0 41.6-19.2 41.6-41.6v-182.4c0-22.4-19.2-41.6-41.6-41.6h-156.8z","M195.2 1024c-105.6 0-195.2-89.6-195.2-195.2 0-108.8 89.6-195.2 195.2-195.2s195.2 89.6 195.2 195.2c3.2 105.6-86.4 195.2-195.2 195.2zM195.2 694.4c-73.6 0-131.2 60.8-131.2 131.2 0 73.6 60.8 134.4 131.2 134.4 73.6 0 131.2-60.8 131.2-131.2 3.2-73.6-57.6-134.4-131.2-134.4z","M828.8 1024c-108.8 0-195.2-89.6-195.2-195.2 0-108.8 89.6-195.2 195.2-195.2s195.2 89.6 195.2 195.2c0 105.6-89.6 195.2-195.2 195.2zM828.8 694.4c-73.6 0-131.2 60.8-131.2 131.2 0 73.6 60.8 131.2 131.2 131.2 73.6 0 131.2-60.8 131.2-131.2s-60.8-131.2-131.2-131.2z","M332.8 640c-6.4 0-12.8 0-16-3.2-16-9.6-19.2-28.8-9.6-44.8l83.2-137.6c9.6-16 28.8-19.2 44.8-9.6s19.2 28.8 9.6 44.8l-83.2 137.6c-6.4 6.4-16 12.8-28.8 12.8z","M691.2 640c-9.6 0-22.4-6.4-28.8-16l-83.2-137.6c-9.6-16-3.2-35.2 9.6-44.8s35.2-3.2 44.8 9.6l83.2 137.6c9.6 16 3.2 35.2-9.6 44.8-6.4 6.4-12.8 6.4-16 6.4z"],"attrs":[{},{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["api"]},"attrs":[{},{},{},{},{}],"properties":{"order":94,"id":206,"name":"api","prevSize":28,"code":59717},"setIdx":2,"setId":1,"iconIdx":94},{"icon":{"paths":["M800 1024h-576c-124.8 0-224-99.2-224-224v-576c0-124.8 99.2-224 224-224h576c124.8 0 224 99.2 224 224v576c0 124.8-99.2 224-224 224zM224 64c-89.6 0-160 70.4-160 160v576c0 89.6 70.4 160 160 160h576c89.6 0 160-70.4 160-160v-576c0-89.6-70.4-160-160-160h-576z","M771.2 860.8h-438.4c-12.8 0-22.4-6.4-28.8-19.2s-3.2-25.6 3.2-35.2l300.8-355.2c6.4-6.4 16-12.8 25.6-12.8s19.2 6.4 25.6 12.8l192 275.2c3.2 3.2 3.2 6.4 3.2 9.6 16 44.8 3.2 73.6-6.4 89.6-22.4 32-70.4 35.2-76.8 35.2zM403.2 796.8h371.2c6.4 0 22.4-3.2 25.6-9.6 3.2-3.2 3.2-12.8 0-25.6l-166.4-236.8-230.4 272z","M332.8 502.4c-76.8 0-140.8-64-140.8-140.8s64-140.8 140.8-140.8 140.8 64 140.8 140.8-60.8 140.8-140.8 140.8zM332.8 284.8c-41.6 0-76.8 32-76.8 76.8s35.2 76.8 76.8 76.8 76.8-35.2 76.8-76.8-32-76.8-76.8-76.8z"],"attrs":[{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["assets"]},"attrs":[{},{},{}],"properties":{"order":95,"id":205,"name":"assets","prevSize":28,"code":59720},"setIdx":2,"setId":1,"iconIdx":95},{"icon":{"paths":["M932.571 548.571c0 20-16.571 36.571-36.571 36.571h-128c0 71.429-15.429 125.143-38.286 165.714l118.857 119.429c14.286 14.286 14.286 37.143 0 51.429-6.857 7.429-16.571 10.857-25.714 10.857s-18.857-3.429-25.714-10.857l-113.143-112.571s-74.857 68.571-172 68.571v-512h-73.143v512c-103.429 0-178.857-75.429-178.857-75.429l-104.571 118.286c-7.429 8-17.143 12-27.429 12-8.571 0-17.143-2.857-24.571-9.143-14.857-13.714-16-36.571-2.857-52l115.429-129.714c-20-39.429-33.143-90.286-33.143-156.571h-128c-20 0-36.571-16.571-36.571-36.571s16.571-36.571 36.571-36.571h128v-168l-98.857-98.857c-14.286-14.286-14.286-37.143 0-51.429s37.143-14.286 51.429 0l98.857 98.857h482.286l98.857-98.857c14.286-14.286 37.143-14.286 51.429 0s14.286 37.143 0 51.429l-98.857 98.857v168h128c20 0 36.571 16.571 36.571 36.571zM658.286 219.429h-365.714c0-101.143 81.714-182.857 182.857-182.857s182.857 81.714 182.857 182.857z"],"attrs":[{}],"width":951,"isMulticolor":false,"isMulticolor2":false,"tags":["bug"],"grid":14},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":28,"code":59709,"name":"bug"},"setIdx":2,"setId":1,"iconIdx":96},{"icon":{"paths":["M585.143 402.286c0 9.714-4 18.857-10.857 25.714l-256 256c-6.857 6.857-16 10.857-25.714 10.857s-18.857-4-25.714-10.857l-256-256c-6.857-6.857-10.857-16-10.857-25.714 0-20 16.571-36.571 36.571-36.571h512c20 0 36.571 16.571 36.571 36.571z"],"attrs":[{}],"width":585,"isMulticolor":false,"isMulticolor2":false,"tags":["caret-down"],"grid":14},"attrs":[{}],"properties":{"order":4,"id":4,"prevSize":28,"code":59692,"name":"caret-down"},"setIdx":2,"setId":1,"iconIdx":97},{"icon":{"paths":["M365.714 256v512c0 20-16.571 36.571-36.571 36.571-9.714 0-18.857-4-25.714-10.857l-256-256c-6.857-6.857-10.857-16-10.857-25.714s4-18.857 10.857-25.714l256-256c6.857-6.857 16-10.857 25.714-10.857 20 0 36.571 16.571 36.571 36.571z"],"attrs":[{}],"width":402,"isMulticolor":false,"isMulticolor2":false,"tags":["caret-left"],"grid":14},"attrs":[{}],"properties":{"order":2,"id":6,"prevSize":28,"code":59690,"name":"caret-left"},"setIdx":2,"setId":1,"iconIdx":98},{"icon":{"paths":["M329.143 512c0 9.714-4 18.857-10.857 25.714l-256 256c-6.857 6.857-16 10.857-25.714 10.857-20 0-36.571-16.571-36.571-36.571v-512c0-20 16.571-36.571 36.571-36.571 9.714 0 18.857 4 25.714 10.857l256 256c6.857 6.857 10.857 16 10.857 25.714z"],"attrs":[{}],"width":329,"isMulticolor":false,"isMulticolor2":false,"tags":["caret-right"],"grid":14},"attrs":[{}],"properties":{"order":1,"id":7,"prevSize":28,"code":59689,"name":"caret-right"},"setIdx":2,"setId":1,"iconIdx":99},{"icon":{"paths":["M585.143 694.857c0 20-16.571 36.571-36.571 36.571h-512c-20 0-36.571-16.571-36.571-36.571 0-9.714 4-18.857 10.857-25.714l256-256c6.857-6.857 16-10.857 25.714-10.857s18.857 4 25.714 10.857l256 256c6.857 6.857 10.857 16 10.857 25.714z"],"attrs":[{}],"width":585,"isMulticolor":false,"isMulticolor2":false,"tags":["caret-up"],"grid":14},"attrs":[{}],"properties":{"order":3,"id":5,"prevSize":28,"code":59691,"name":"caret-up"},"setIdx":2,"setId":1,"iconIdx":100},{"icon":{"paths":["M800 1024h-576c-124.8 0-224-99.2-224-224v-576c0-124.8 99.2-224 224-224h576c124.8 0 224 99.2 224 224v576c0 124.8-99.2 224-224 224zM224 64c-89.6 0-160 70.4-160 160v576c0 89.6 70.4 160 160 160h576c89.6 0 160-70.4 160-160v-576c0-89.6-70.4-160-160-160h-576z","M480 448h-211.2c-57.6 0-105.6-48-105.6-105.6v-73.6c0-57.6 48-105.6 105.6-105.6h211.2c57.6 0 105.6 48 105.6 105.6v73.6c0 57.6-48 105.6-105.6 105.6zM268.8 227.2c-22.4 0-41.6 19.2-41.6 41.6v73.6c0 22.4 19.2 41.6 41.6 41.6h211.2c22.4 0 41.6-19.2 41.6-41.6v-73.6c0-22.4-19.2-41.6-41.6-41.6h-211.2z","M828.8 611.2h-633.6c-19.2 0-32-12.8-32-32s12.8-32 32-32h630.4c19.2 0 32 12.8 32 32s-12.8 32-28.8 32z","M553.6 777.6h-358.4c-19.2 0-32-12.8-32-32s12.8-32 32-32h355.2c19.2 0 32 12.8 32 32s-12.8 32-28.8 32z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["content"]},"attrs":[{},{},{},{}],"properties":{"order":93,"id":204,"name":"contents, trigger-ContentChanged","prevSize":28,"code":59718},"setIdx":2,"setId":1,"iconIdx":101},{"icon":{"paths":["M947.2 102.4h-128v-25.6c0-14.131-11.469-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-512v-25.6c0-14.131-11.52-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-128c-42.342 0-76.8 34.458-76.8 76.8v716.8c0 42.342 34.458 76.8 76.8 76.8h870.4c42.342 0 76.8-34.458 76.8-76.8v-716.8c0-42.342-34.458-76.8-76.8-76.8zM972.8 896c0 14.131-11.469 25.6-25.6 25.6h-870.4c-14.080 0-25.6-11.469-25.6-25.6v-537.6h921.6v537.6zM972.8 307.2h-921.6v-128c0-14.080 11.52-25.6 25.6-25.6h128v76.8c0 14.080 11.52 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h512v76.8c0 14.080 11.469 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h128c14.131 0 25.6 11.52 25.6 25.6v128zM332.8 512h51.2c14.080 0 25.6-11.52 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.52-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.52-25.6 25.6s11.52 25.6 25.6 25.6zM640 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.52-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.52-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 614.4h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 614.4h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 716.8h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 716.8h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 819.2h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 819.2h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["control-date"]},"attrs":[{}],"properties":{"order":71,"id":42,"name":"control-Date","prevSize":28,"code":59702},"setIdx":2,"setId":1,"iconIdx":102},{"icon":{"paths":["M486.4 409.6h51.2c14.080 0 25.6 11.52 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.52-25.6-25.6s11.52-25.6 25.6-25.6zM230.4 614.4c14.080 0 25.6 11.469 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.469-25.6-25.6s11.52-25.6 25.6-25.6h51.2zM230.4 512c14.080 0 25.6 11.469 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.469-25.6-25.6s11.52-25.6 25.6-25.6h51.2zM51.2 742.4v-435.2h665.6v102.4h51.2v-281.6c0-42.342-34.458-76.8-76.8-76.8h-128v-25.6c0-14.131-11.469-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-256v-25.6c0-14.131-11.52-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-128c-42.342 0-76.8 34.458-76.8 76.8v614.4c0 42.342 34.458 76.8 76.8 76.8h332.8v-51.2h-332.8c-14.080 0-25.6-11.469-25.6-25.6zM51.2 128c0-14.080 11.52-25.6 25.6-25.6h128v76.8c0 14.080 11.52 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h256v76.8c0 14.080 11.469 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h128c14.131 0 25.6 11.52 25.6 25.6v128h-665.6v-128zM384 409.6c14.080 0 25.6 11.52 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.52-25.6-25.6s11.52-25.6 25.6-25.6h51.2zM742.4 460.8c-155.546 0-281.6 126.054-281.6 281.6s126.054 281.6 281.6 281.6 281.6-126.054 281.6-281.6-126.054-281.6-281.6-281.6zM742.4 972.8c-127.232 0-230.4-103.168-230.4-230.4s103.168-230.4 230.4-230.4 230.4 103.168 230.4 230.4-103.168 230.4-230.4 230.4zM384 512c14.080 0 25.6 11.469 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.469-25.6-25.6s11.52-25.6 25.6-25.6h51.2zM384 614.4c14.080 0 25.6 11.469 25.6 25.6s-11.52 25.6-25.6 25.6h-51.2c-14.080 0-25.6-11.469-25.6-25.6s11.52-25.6 25.6-25.6h51.2zM844.8 716.8c14.131 0 25.6 11.469 25.6 25.6s-11.469 25.6-25.6 25.6h-102.4c-14.131 0-25.6-11.469-25.6-25.6v-102.4c0-14.131 11.469-25.6 25.6-25.6s25.6 11.469 25.6 25.6v76.8h76.8z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["control-date-time"]},"attrs":[{}],"properties":{"order":70,"id":41,"name":"control-DateTime","prevSize":28,"code":59703},"setIdx":2,"setId":1,"iconIdx":103},{"icon":{"paths":["M793.6 609.416h-61.838v-28.108h-0.783q-21.135 33.092-62.034 33.092-37.573 0-60.469-26.912-22.896-27.112-22.896-75.554 0-50.635 25.244-81.136t66.144-30.501q38.747 0 54.011 28.308h0.783v-121.405h61.838v302.216zM732.936 510.139v-15.35q0-19.935-11.35-33.092t-29.549-13.157q-20.548 0-32.093 16.546-11.546 16.347-11.546 45.053 0 26.912 11.154 41.465t30.919 14.553q18.786 0 30.528-15.35 11.937-15.35 11.937-40.668zM548.594 609.416h-61.643v-116.421q0-44.455-32.093-44.455-15.264 0-24.853 13.357t-9.589 33.292v114.228h-61.839v-117.617q0-43.259-31.506-43.259-15.851 0-25.44 12.758-9.393 12.758-9.393 34.687v113.431h-61.838v-204.135h61.838v31.896h0.783q9.589-16.347 26.81-26.514 17.417-10.366 37.964-10.366 42.465 0 58.12 38.076 22.896-38.076 67.318-38.076 65.361 0 65.361 82.133v126.987zM0 0v204.8h76.8v76.8h51.2v-76.8h76.8v-204.8zM819.2 0v204.8h204.8v-204.8zM51.2 51.2h102.4v102.4h-102.4zM870.4 51.2h102.4v102.4h-102.4zM281.6 76.8v51.2h102.4v-51.2zM486.4 76.8v51.2h102.4v-51.2zM691.2 76.8v51.2h102.4v-51.2zM896 281.6v102.4h51.2v-102.4zM76.8 384v102.4h51.2v-102.4zM896 486.4v102.4h51.2v-102.4zM76.8 588.8v102.4h51.2v-102.4zM896 691.2v102.4h51.2v-102.4zM76.8 793.6v25.6h-76.8v204.8h204.8v-76.8h76.8v-51.2h-76.8v-76.8h-76.8v-25.6zM819.2 819.2v76.8h-25.6v51.2h25.6v76.8h204.8v-204.8zM51.2 870.4h102.4v102.4h-102.4zM870.4 870.4h102.4v102.4h-102.4zM384 896v51.2h102.4v-51.2zM588.8 896v51.2h102.4v-51.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["control-Markdown"]},"attrs":[{}],"properties":{"order":72,"id":43,"name":"control-Markdown","prevSize":28,"code":59704},"setIdx":2,"setId":1,"iconIdx":104},{"icon":{"paths":["M292.571 713.143v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM292.571 420.571v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM658.286 713.143v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM292.571 128v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM658.286 420.571v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM1024 713.143v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM658.286 128v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM1024 420.571v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857zM1024 128v109.714c0 30.286-24.571 54.857-54.857 54.857h-182.857c-30.286 0-54.857-24.571-54.857-54.857v-109.714c0-30.286 24.571-54.857 54.857-54.857h182.857c30.286 0 54.857 24.571 54.857 54.857z"],"width":1024,"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["th"],"defaultCode":61450,"grid":14},"attrs":[],"properties":{"name":"grid","id":14,"order":83,"prevSize":28,"code":61450},"setIdx":2,"setId":1,"iconIdx":105},{"icon":{"paths":["M877.714 768v73.143c0 20-16.571 36.571-36.571 36.571h-804.571c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h804.571c20 0 36.571 16.571 36.571 36.571zM877.714 475.429v73.143c0 20-16.571 36.571-36.571 36.571h-804.571c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h804.571c20 0 36.571 16.571 36.571 36.571zM877.714 182.857v73.143c0 20-16.571 36.571-36.571 36.571h-804.571c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h804.571c20 0 36.571 16.571 36.571 36.571z"],"width":877.7142857142857,"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["bars","navicon","reorder"],"defaultCode":61641,"grid":14},"attrs":[],"properties":{"name":"list1","id":178,"order":89,"prevSize":28,"code":61641},"setIdx":2,"setId":1,"iconIdx":106},{"icon":{"paths":["M512 64c-131.696 0-239.125 107.4-239.125 239 0 65.8 24.831 146.717 65.375 215.25 19.653 33.221 43.902 63.853 71.75 87.125-59.423 7.524-122.009 9.415-172.125 32-79.809 35.967-144.343 94.74-172.375 178.625-1.5 9.499 0 0-1.5 9v0.499c0 73.995 60.563 134.501 134.375 134.501h627.125c73.888 0 134.5-60.506 134.5-134.5l-1.5-9.375c-27.845-84.263-92.273-143.119-172.125-179-50.17-22.544-112.844-24.421-172.375-31.875 27.792-23.26 52.002-53.831 71.625-87 40.544-68.533 65.375-149.45 65.375-215.25 0-131.6-107.304-239-239-239zM512 124c99.241 0 179 79.875 179 179 0 49.562-21.877 125.381-57 184.75s-81.435 98.75-122 98.75c-40.565 0-86.877-39.381-122-98.75s-57.125-135.188-57.125-184.75c0-99.125 79.884-179 179.125-179zM512 646.5c92.551 0 180.829 14.406 249.75 45.375 66.784 30.009 113.649 74.724 136.5 137.75-2.447 39.259-32.9 70.375-72.75 70.375h-627.125c-39.678 0-70.116-31.051-72.625-70.25 22.978-62.705 69.953-107.523 136.75-137.625 68.937-31.067 157.205-45.625 249.5-45.625z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["user-o"]},"attrs":[{}],"properties":{"order":64,"id":38,"name":"user-o","prevSize":28,"code":59698},"setIdx":2,"setId":1,"iconIdx":107},{"icon":{"paths":["M217.6 992c-3.2 0-3.2 0-6.4 0h-3.2c-144-25.6-208-144-208-249.6 0-99.2 57.6-208 185.6-240v-147.2c0-19.2 12.8-32 32-32s32 12.8 32 32v172.8c0 16-12.8 28.8-25.6 32-108.8 16-160 102.4-160 182.4s48 166.4 153.6 185.6h6.4c16 3.2 28.8 19.2 25.6 38.4-3.2 16-16 25.6-32 25.6z","M774.4 1001.6c0 0 0 0 0 0-102.4 0-211.2-60.8-243.2-185.6h-176c-19.2 0-32-12.8-32-32s12.8-32 32-32h201.6c16 0 28.8 12.8 32 25.6 16 108.8 102.4 156.8 182.4 160 80 0 166.4-48 185.6-153.6v-3.2c3.2-16 19.2-28.8 38.4-25.6 16 3.2 28.8 19.2 25.6 38.4v3.2c-22.4 140.8-140.8 204.8-246.4 204.8z","M787.2 678.4c-19.2 0-32-12.8-32-32v-176c0-16 12.8-28.8 25.6-32 108.8-16 156.8-102.4 160-182.4 0-80-48-166.4-153.6-185.6h-3.2c-19.2-6.4-32-22.4-28.8-38.4s19.2-28.8 38.4-25.6h3.2c144 25.6 208 144 208 249.6 0 99.2-60.8 208-185.6 240v150.4c0 16-16 32-32 32z","M41.6 246.4c-3.2 0-3.2 0-6.4 0-16-3.2-28.8-19.2-25.6-35.2v-3.2c25.6-144 140.8-208 246.4-208 0 0 3.2 0 3.2 0 99.2 0 208 60.8 240 185.6h147.2c19.2 0 32 12.8 32 32s-12.8 32-32 32h-172.8c-16 0-28.8-12.8-32-25.6-16-108.8-102.4-156.8-182.4-160-80 0-166.4 48-185.6 153.6v3.2c-3.2 16-16 25.6-32 25.6z","M256 387.2c-32 0-67.2-12.8-92.8-38.4-51.2-51.2-51.2-134.4 0-185.6 25.6-22.4 57.6-35.2 92.8-35.2s67.2 12.8 92.8 38.4c25.6 25.6 38.4 57.6 38.4 92.8s-12.8 67.2-38.4 92.8c-25.6 22.4-57.6 35.2-92.8 35.2zM256 192c-16 0-32 6.4-44.8 19.2-25.6 25.6-25.6 67.2 0 92.8s67.2 25.6 92.8 0c12.8-12.8 19.2-28.8 19.2-48s-6.4-32-19.2-44.8-28.8-19.2-48-19.2z","M771.2 873.6c-32 0-67.2-12.8-92.8-38.4-51.2-51.2-51.2-134.4 0-185.6 25.6-25.6 57.6-38.4 92.8-38.4s67.2 12.8 92.8 38.4c25.6 25.6 38.4 57.6 38.4 92.8s-12.8 67.2-38.4 92.8c-28.8 25.6-60.8 38.4-92.8 38.4zM771.2 678.4c-19.2 0-35.2 6.4-48 19.2-25.6 25.6-25.6 67.2 0 92.8s67.2 25.6 92.8 0c12.8-12.8 19.2-28.8 19.2-48s-6.4-35.2-19.2-48-28.8-16-44.8-16z","M745.6 387.2c-32 0-67.2-12.8-92.8-38.4s-38.4-57.6-38.4-92.8 12.8-67.2 38.4-92.8c25.6-22.4 60.8-35.2 92.8-35.2s67.2 12.8 92.8 38.4c51.2 51.2 51.2 134.4 0 185.6v0c-25.6 22.4-57.6 35.2-92.8 35.2zM745.6 192c-19.2 0-35.2 6.4-48 19.2s-19.2 28.8-19.2 48 6.4 35.2 19.2 48c25.6 25.6 67.2 25.6 92.8 0s25.6-67.2 0-92.8c-9.6-16-25.6-22.4-44.8-22.4z","M259.2 873.6c-32 0-67.2-12.8-92.8-38.4s-38.4-57.6-38.4-92.8 12.8-67.2 38.4-92.8c25.6-22.4 57.6-35.2 92.8-35.2s67.2 12.8 92.8 38.4c51.2 51.2 51.2 134.4 0 185.6v0c-25.6 22.4-57.6 35.2-92.8 35.2zM259.2 678.4c-19.2 0-35.2 6.4-48 19.2s-19.2 28.8-19.2 48 6.4 35.2 19.2 48c25.6 25.6 67.2 25.6 92.8 0s25.6-67.2 0-92.8c-9.6-16-25.6-22.4-44.8-22.4z"],"attrs":[{},{},{},{},{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":14,"tags":["webhooks"]},"attrs":[{},{},{},{},{},{},{},{}],"properties":{"order":92,"id":203,"name":"rules","prevSize":28,"code":59719},"setIdx":2,"setId":1,"iconIdx":108},{"icon":{"paths":["M810.667 85.333h-597.333c-72.533 0-128 55.467-128 128v597.333c0 72.533 55.467 128 128 128h597.333c72.533 0 128-55.467 128-128v-597.333c0-72.533-55.467-128-128-128zM853.333 810.667c0 25.6-17.067 42.667-42.667 42.667h-597.333c-25.6 0-42.667-17.067-42.667-42.667v-597.333c0-25.6 17.067-42.667 42.667-42.667h597.333c25.6 0 42.667 17.067 42.667 42.667v597.333z","M682.667 469.333h-341.333c-25.6 0-42.667 17.067-42.667 42.667s17.067 42.667 42.667 42.667h341.333c25.6 0 42.667-17.067 42.667-42.667s-17.067-42.667-42.667-42.667z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"tags":["minus-square"],"grid":24},"attrs":[{},{}],"properties":{"order":1,"id":1,"prevSize":24,"code":59753,"name":"minus-square"},"setIdx":1,"setId":2,"iconIdx":0},{"icon":{"paths":["M810.667 85.333h-597.333c-72.533 0-128 55.467-128 128v597.333c0 72.533 55.467 128 128 128h597.333c72.533 0 128-55.467 128-128v-597.333c0-72.533-55.467-128-128-128zM853.333 810.667c0 25.6-17.067 42.667-42.667 42.667h-597.333c-25.6 0-42.667-17.067-42.667-42.667v-597.333c0-25.6 17.067-42.667 42.667-42.667h597.333c25.6 0 42.667 17.067 42.667 42.667v597.333z","M682.667 469.333h-128v-128c0-25.6-17.067-42.667-42.667-42.667s-42.667 17.067-42.667 42.667v128h-128c-25.6 0-42.667 17.067-42.667 42.667s17.067 42.667 42.667 42.667h128v128c0 25.6 17.067 42.667 42.667 42.667s42.667-17.067 42.667-42.667v-128h128c25.6 0 42.667-17.067 42.667-42.667s-17.067-42.667-42.667-42.667z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"tags":["plus-square"],"grid":24},"attrs":[{},{}],"properties":{"order":1,"id":0,"name":"plus-square","prevSize":24,"code":59752},"setIdx":1,"setId":2,"iconIdx":1},{"icon":{"paths":["M170 640v-86h684v86h-684zM854 384v86h-684v-86h684z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["drag_handle"],"grid":24},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":24,"code":59745,"name":"drag2"},"setIdx":2,"setId":1,"iconIdx":6},{"icon":{"paths":["M854 682v-512h-684v598l86-86h598zM854 86c46 0 84 38 84 84v512c0 46-38 86-84 86h-598l-170 170v-768c0-46 38-84 84-84h684z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["chat_bubble_outline"],"grid":24},"attrs":[{}],"properties":{"order":1,"id":0,"name":"comments","prevSize":24,"code":59743},"setIdx":2,"setId":1,"iconIdx":7},{"icon":{"paths":["M512 128c212 0 384 172 384 384s-172 384-384 384c-88 0-170-30-234-80l60-60c50 34 110 54 174 54 166 0 298-132 298-298s-132-298-298-298-298 132-298 298h128l-172 170-170-170h128c0-212 172-384 384-384zM598 512c0 46-40 86-86 86s-86-40-86-86 40-86 86-86 86 40 86 86z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["settings_backup_restore"],"grid":24},"attrs":[{}],"properties":{"order":1,"id":2,"prevSize":24,"code":59739,"name":"backup"},"setIdx":2,"setId":1,"iconIdx":8},{"icon":{"paths":["M726 512c0 24-20 42-44 42h-426l-170 172v-598c0-24 18-42 42-42h554c24 0 44 18 44 42v384zM896 256c24 0 42 18 42 42v640l-170-170h-470c-24 0-42-18-42-42v-86h554v-384h86z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["question_answer"],"grid":24},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":24,"code":59738,"name":"support"},"setIdx":2,"setId":1,"iconIdx":9},{"icon":{"paths":["M918 384v128h-128v298h-128v-298h-128v-128h384zM106 170h556v128h-214v512h-128v-512h-214v-128z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["text_fields"],"grid":24},"attrs":[{}],"properties":{"order":75,"id":0,"prevSize":24,"code":59705,"name":"control-RichText"},"setIdx":2,"setId":1,"iconIdx":10},{"icon":{"paths":["M640 85.333q78 0 149.167 30.5t122.5 81.833 81.833 122.5 30.5 149.167q0 85-35 160.667t-96.667 129.167-140 77.5l21-20.667q18-18.333 28-42.667 9.333-22.667 9.333-49.333 0-6.667-0.333-9.333 59.333-41.333 93.833-105.833t34.5-139.5q0-60.667-23.667-116t-63.667-95.333-95.333-63.667-116-23.667q-55.333 0-106.5 19.833t-90 53.833-65 81.333-33.833 101h-88.667q-70.667 0-120.667 50t-50 120.667q0 38.667 15.167 71.667t39.833 54.167 54.833 33 60.833 11.833h50q11.667 29.333 30 48l37.667 37.333h-117.667q-69.667 0-128.5-34.333t-93.167-93.167-34.333-128.5 34.333-128.5 93.167-93.167 128.5-34.333h22q26.333-74.333 79.333-132.167t126.833-90.833 155.833-33zM554.667 426.667q17.667 0 30.167 12.5t12.5 30.167v281l55-55.333q12.333-12.333 30.333-12.333 18.333 0 30.5 12.167t12.167 30.5q0 18-12.333 30.333l-128 128q-12.333 12.333-30.333 12.333t-30.333-12.333l-128-128q-12.333-13-12.333-30.333 0-17.667 12.5-30.167t30.167-12.5q18 0 30.333 12.333l55 55.333v-281q0-17.667 12.5-30.167t30.167-12.5z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["cloud-download"],"grid":24},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":24,"code":59710,"name":"download"},"setIdx":2,"setId":1,"iconIdx":11},{"icon":{"paths":["M585.143 548.557c0 9.728-3.986 18.871-10.862 25.71l-256 256c-6.839 6.839-16.018 10.862-25.71 10.862s-18.871-3.986-25.71-10.862l-256-256c-6.839-6.839-10.862-16.018-10.862-25.71 0-20.005 16.567-36.571 36.571-36.571h512c20.005 0 36.571 16.567 36.571 36.571z","M585.143 219.443c0 9.728-3.986 18.871-10.862 25.71l-256 256c-6.839 6.839-16.018 10.862-25.71 10.862s-18.871-3.986-25.71-10.862l-256-256c-6.839-6.839-10.862-16.018-10.862-25.71 0-20.005 16.567-36.571 36.571-36.571h512c20.005 0 36.571 16.567 36.571 36.571z"],"attrs":[{},{}],"width":585,"isMulticolor":false,"isMulticolor2":false,"grid":16,"tags":["caret-bottom"]},"attrs":[{},{}],"properties":{"order":125,"id":226,"name":"caret-bottom","prevSize":24,"code":59755},"setIdx":2,"setId":1,"iconIdx":0},{"icon":{"paths":["M585.143 804.577c0 20.005-16.567 36.571-36.571 36.571h-512c-20.005 0-36.571-16.567-36.571-36.571 0-9.728 3.986-18.871 10.862-25.71l256-256c6.839-6.839 16.018-10.862 25.71-10.862s18.871 3.986 25.71 10.862l256 256c6.839 6.839 10.862 16.018 10.862 25.71z","M585.143 475.423c0 20.005-16.567 36.571-36.571 36.571h-512c-20.005 0-36.571-16.567-36.571-36.571 0-9.728 3.986-18.871 10.862-25.71l256-256c6.839-6.839 16.018-10.862 25.71-10.862s18.871 3.986 25.71 10.862l256 256c6.839 6.839 10.862 16.018 10.862 25.71z"],"attrs":[{},{}],"width":585,"isMulticolor":false,"isMulticolor2":false,"grid":16,"tags":["caret-top"]},"attrs":[{},{}],"properties":{"order":124,"id":225,"name":"caret-top","prevSize":24,"code":59756},"setIdx":2,"setId":1,"iconIdx":1},{"icon":{"paths":["M256 102.4c-28.314 0-51.2 22.886-51.2 51.2v256h51.2v-256h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-512v-460.8h-51.2v460.8c0 28.314 22.886 51.2 51.2 51.2h512c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-292.2zM614.4 189.8l117.4 117.4h-117.4z","M408.906 587.72l-35.3-37 138.1-131.9 138 131.9-35.3 37-102.7-98.1z","M511.706 773.12l-138.1-131.9 35.3-37 102.8 98.1 102.7-98.1 35.3 37z"],"attrs":[{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":16,"tags":["show"]},"attrs":[{},{},{}],"properties":{"order":123,"id":224,"name":"show","prevSize":24,"code":59748},"setIdx":2,"setId":1,"iconIdx":2},{"icon":{"paths":["M256 102.4c-28.314 0-51.2 22.886-51.2 51.2v256h51.2v-256h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-512v-460.8h-51.2v460.8c0 28.314 22.886 51.2 51.2 51.2h512c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-292.2zM614.4 189.8l117.4 117.4h-117.4z","M348.394 15.988c-28.314 0-51.2 22.886-51.2 51.2v23.7h51.2v-23.7h307.2l204.8 204.8v512h-23.8v51.2h23.8c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-292.2z","M408.906 587.72l-35.3-37 138.1-131.9 138 131.9-35.3 37-102.7-98.1z","M511.706 773.12l-138.1-131.9 35.3-37 102.8 98.1 102.7-98.1 35.3 37z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":16,"tags":["show-all"]},"attrs":[{},{},{},{}],"properties":{"order":122,"id":223,"name":"show-all","prevSize":24,"code":59749},"setIdx":2,"setId":1,"iconIdx":3},{"icon":{"paths":["M256 102.4c-28.314 0-51.2 22.886-51.2 51.2v256h51.2v-256h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-512v-460.8h-51.2v460.8c0 28.314 22.886 51.2 51.2 51.2h512c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-292.2zM614.4 189.8l117.4 117.4h-117.4z","M408.9 418.8l-35.3 37 138 131.9 138.1-131.9-35.3-37-102.8 98.1z","M511.6 604.2l-138 131.9 35.3 37 102.7-98.1 102.8 98.1 35.3-37z"],"attrs":[{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":16,"tags":["hide"]},"attrs":[{},{},{}],"properties":{"order":121,"id":222,"name":"hide","prevSize":24,"code":59750},"setIdx":2,"setId":1,"iconIdx":4},{"icon":{"paths":["M408.9 418.8l-35.3 37 138.1 131.9 138-131.9-35.3-37-102.7 98.1z","M511.7 604.2l-138.1 131.9 35.3 37 102.8-98.1 102.7 98.1 35.3-37z","M256 102.4c-28.314 0-51.2 22.886-51.2 51.2v256h51.2v-256h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-512v-460.8h-51.2v460.8c0 28.314 22.886 51.2 51.2 51.2h512c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-292.2zM614.4 189.8l117.4 117.4h-117.4z","M348.394 15.988c-28.314 0-51.2 22.886-51.2 51.2v23.7h51.2v-23.7h307.2l204.8 204.8v512h-23.8v51.2h23.8c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-292.2z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":16,"tags":["hide-all"]},"attrs":[{},{},{},{}],"properties":{"order":120,"id":221,"name":"hide-all","prevSize":24,"code":59751},"setIdx":2,"setId":1,"iconIdx":5},{"icon":{"paths":["M512 1024c-136.76 0-265.334-53.258-362.040-149.96-96.702-96.706-149.96-225.28-149.96-362.040 0-96.838 27.182-191.134 78.606-272.692 50-79.296 120.664-143.372 204.356-185.3l43 85.832c-68.038 34.084-125.492 86.186-166.15 150.67-41.746 66.208-63.812 142.798-63.812 221.49 0 229.382 186.618 416 416 416s416-186.618 416-416c0-78.692-22.066-155.282-63.81-221.49-40.66-64.484-98.114-116.584-166.15-150.67l43-85.832c83.692 41.928 154.358 106.004 204.356 185.3 51.422 81.558 78.604 175.854 78.604 272.692 0 136.76-53.258 265.334-149.96 362.040-96.706 96.702-225.28 149.96-362.040 149.96z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["spinner","loading","loading-wheel","busy","wait"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":24,"code":59737,"name":"spinner2"},"setIdx":2,"setId":1,"iconIdx":12},{"icon":{"paths":["M1024 397.050l-353.78-51.408-158.22-320.582-158.216 320.582-353.784 51.408 256 249.538-60.432 352.352 316.432-166.358 316.432 166.358-60.434-352.352 256.002-249.538z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["star-full","rate","star","favorite","bookmark"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":2,"prevSize":24,"code":59741,"name":"star-full"},"setIdx":2,"setId":1,"iconIdx":13},{"icon":{"paths":["M1024 397.050l-353.78-51.408-158.22-320.582-158.216 320.582-353.784 51.408 256 249.538-60.432 352.352 316.432-166.358 316.432 166.358-60.434-352.352 256.002-249.538zM512 753.498l-223.462 117.48 42.676-248.83-180.786-176.222 249.84-36.304 111.732-226.396 111.736 226.396 249.836 36.304-180.788 176.222 42.678 248.83-223.462-117.48z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["star-empty","rate","star","favorite","bookmark"],"grid":16},"attrs":[{}],"properties":{"order":2,"id":1,"prevSize":24,"code":59742,"name":"star-empty"},"setIdx":2,"setId":1,"iconIdx":14},{"icon":{"paths":["M1024 226.4c-37.6 16.8-78.2 28-120.6 33 43.4-26 76.6-67.2 92.4-116.2-40.6 24-85.6 41.6-133.4 51-38.4-40.8-93-66.2-153.4-66.2-116 0-210 94-210 210 0 16.4 1.8 32.4 5.4 47.8-174.6-8.8-329.4-92.4-433-219.6-18 31-28.4 67.2-28.4 105.6 0 72.8 37 137.2 93.4 174.8-34.4-1-66.8-10.6-95.2-26.2 0 0.8 0 1.8 0 2.6 0 101.8 72.4 186.8 168.6 206-17.6 4.8-36.2 7.4-55.4 7.4-13.6 0-26.6-1.4-39.6-3.8 26.8 83.4 104.4 144.2 196.2 146-72 56.4-162.4 90-261 90-17 0-33.6-1-50.2-3 93.2 59.8 203.6 94.4 322.2 94.4 386.4 0 597.8-320.2 597.8-597.8 0-9.2-0.2-18.2-0.6-27.2 41-29.4 76.6-66.4 104.8-108.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["twitter","brand","tweet","social"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":24,"code":59740,"name":"twitter"},"setIdx":2,"setId":1,"iconIdx":15},{"icon":{"paths":["M728.992 512c137.754-87.334 231.008-255.208 231.008-448 0-21.676-1.192-43.034-3.478-64h-889.042c-2.29 20.968-3.48 42.326-3.48 64 0 192.792 93.254 360.666 231.006 448-137.752 87.334-231.006 255.208-231.006 448 0 21.676 1.19 43.034 3.478 64h889.042c2.288-20.966 3.478-42.324 3.478-64 0.002-192.792-93.252-360.666-231.006-448zM160 960c0-186.912 80.162-345.414 224-397.708v-100.586c-143.838-52.29-224-210.792-224-397.706v0h704c0 186.914-80.162 345.416-224 397.706v100.586c143.838 52.294 224 210.796 224 397.708h-704zM619.626 669.594c-71.654-40.644-75.608-93.368-75.626-125.366v-64.228c0-31.994 3.804-84.914 75.744-125.664 38.504-22.364 71.808-56.348 97.048-98.336h-409.582c25.266 42.032 58.612 76.042 97.166 98.406 71.654 40.644 75.606 93.366 75.626 125.366v64.228c0 31.992-3.804 84.914-75.744 125.664-72.622 42.18-126.738 125.684-143.090 226.336h501.67c-16.364-100.708-70.53-184.248-143.212-226.406z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["hour-glass","loading","busy","wait"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":24,"code":59732,"name":"hour-glass"},"setIdx":2,"setId":1,"iconIdx":16},{"icon":{"paths":["M192 512c0-12.18 0.704-24.196 2.030-36.022l-184.98-60.104c-5.916 31.14-9.050 63.264-9.050 96.126 0 147.23 62.166 279.922 161.654 373.324l114.284-157.296c-52.124-56.926-83.938-132.758-83.938-216.028zM832 512c0 83.268-31.812 159.102-83.938 216.028l114.284 157.296c99.488-93.402 161.654-226.094 161.654-373.324 0-32.862-3.132-64.986-9.048-96.126l-184.98 60.104c1.324 11.828 2.028 23.842 2.028 36.022zM576 198.408c91.934 18.662 169.544 76.742 214.45 155.826l184.978-60.102c-73.196-155.42-222.24-268.060-399.428-290.156v194.432zM233.55 354.232c44.906-79.084 122.516-137.164 214.45-155.826v-194.43c-177.188 22.096-326.23 134.736-399.426 290.154l184.976 60.102zM644.556 803.328c-40.39 18.408-85.272 28.672-132.556 28.672s-92.166-10.264-132.554-28.67l-114.292 157.31c73.206 40.366 157.336 63.36 246.846 63.36s173.64-22.994 246.848-63.36l-114.292-157.312z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["spinner","loading","loading-wheel","busy","wait"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":24,"code":59731,"name":"spinner"},"setIdx":2,"setId":1,"iconIdx":17},{"icon":{"paths":["M658.744 749.256l-210.744-210.746v-282.51h128v229.49l173.256 173.254zM512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 896c-212.078 0-384-171.922-384-384s171.922-384 384-384c212.078 0 384 171.922 384 384s-171.922 384-384 384z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["clock","time","schedule"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":24,"code":59728,"name":"clock"},"setIdx":2,"setId":1,"iconIdx":18},{"icon":{"paths":["M128 320v640c0 35.2 28.8 64 64 64h576c35.2 0 64-28.8 64-64v-640h-704zM320 896h-64v-448h64v448zM448 896h-64v-448h64v448zM576 896h-64v-448h64v448zM704 896h-64v-448h64v448z","M848 128h-208v-80c0-26.4-21.6-48-48-48h-224c-26.4 0-48 21.6-48 48v80h-208c-26.4 0-48 21.6-48 48v80h832v-80c0-26.4-21.6-48-48-48zM576 128h-192v-63.198h192v63.198z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"tags":["bin","trashcan","remove","delete","recycle","dispose"],"grid":16},"attrs":[{},{}],"properties":{"order":1,"id":0,"name":"bin2","prevSize":24,"code":59650},"setIdx":2,"setId":1,"iconIdx":19},{"icon":{"paths":["M512 0c-282.77 0-512 229.23-512 512s229.23 512 512 512 512-229.23 512-512-229.23-512-512-512zM512 960.002c-62.958 0-122.872-13.012-177.23-36.452l233.148-262.29c5.206-5.858 8.082-13.422 8.082-21.26v-96c0-17.674-14.326-32-32-32-112.99 0-232.204-117.462-233.374-118.626-6-6.002-14.14-9.374-22.626-9.374h-128c-17.672 0-32 14.328-32 32v192c0 12.122 6.848 23.202 17.69 28.622l110.31 55.156v187.886c-116.052-80.956-192-215.432-192-367.664 0-68.714 15.49-133.806 43.138-192h116.862c8.488 0 16.626-3.372 22.628-9.372l128-128c6-6.002 9.372-14.14 9.372-22.628v-77.412c40.562-12.074 83.518-18.588 128-18.588 70.406 0 137.004 16.26 196.282 45.2-4.144 3.502-8.176 7.164-12.046 11.036-36.266 36.264-56.236 84.478-56.236 135.764s19.97 99.5 56.236 135.764c36.434 36.432 85.218 56.264 135.634 56.26 3.166 0 6.342-0.080 9.518-0.236 13.814 51.802 38.752 186.656-8.404 372.334-0.444 1.744-0.696 3.488-0.842 5.224-81.324 83.080-194.7 134.656-320.142 134.656z"],"attrs":[],"isMulticolor":false,"isMulticolor2":false,"tags":["earth","globe","language","web","internet","sphere","planet"],"defaultCode":59850,"grid":16},"attrs":[],"properties":{"ligatures":"earth, globe2","name":"earth","id":202,"order":91,"prevSize":24,"code":59850},"setIdx":2,"setId":1,"iconIdx":20},{"icon":{"paths":["M512.002 193.212v-65.212h128v-64c0-35.346-28.654-64-64.002-64h-191.998c-35.346 0-64 28.654-64 64v64h128v65.212c-214.798 16.338-384 195.802-384 414.788 0 229.75 186.25 416 416 416s416-186.25 416-416c0-218.984-169.202-398.448-384-414.788zM706.276 834.274c-60.442 60.44-140.798 93.726-226.274 93.726s-165.834-33.286-226.274-93.726c-60.44-60.44-93.726-140.8-93.726-226.274s33.286-165.834 93.726-226.274c58.040-58.038 134.448-91.018 216.114-93.548l-21.678 314.020c-1.86 26.29 12.464 37.802 31.836 37.802s33.698-11.512 31.836-37.802l-21.676-314.022c81.666 2.532 158.076 35.512 216.116 93.55 60.44 60.44 93.726 140.8 93.726 226.274s-33.286 165.834-93.726 226.274z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["stopwatch","time","speed","meter","chronometer"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":2,"prevSize":24,"code":59715,"name":"elapsed"},"setIdx":2,"setId":1,"iconIdx":21},{"icon":{"paths":["M522.2 438.8v175.6h290.4c-11.8 75.4-87.8 220.8-290.4 220.8-174.8 0-317.4-144.8-317.4-323.2s142.6-323.2 317.4-323.2c99.4 0 166 42.4 204 79l139-133.8c-89.2-83.6-204.8-134-343-134-283 0-512 229-512 512s229 512 512 512c295.4 0 491.6-207.8 491.6-500.2 0-33.6-3.6-59.2-8-84.8l-483.6-0.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["google","brand"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":24,"code":59707,"name":"google"},"setIdx":2,"setId":1,"iconIdx":22},{"icon":{"paths":["M592 448h-16v-192c0-105.87-86.13-192-192-192h-128c-105.87 0-192 86.13-192 192v192h-16c-26.4 0-48 21.6-48 48v480c0 26.4 21.6 48 48 48h544c26.4 0 48-21.6 48-48v-480c0-26.4-21.6-48-48-48zM192 256c0-35.29 28.71-64 64-64h128c35.29 0 64 28.71 64 64v192h-256v-192z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["lock","secure","private","encrypted"],"grid":16},"attrs":[{}],"properties":{"order":2,"id":0,"prevSize":24,"code":59700,"name":"lock"},"setIdx":2,"setId":1,"iconIdx":23},{"icon":{"paths":["M0.35 512l-0.35-312.074 384-52.144v364.218zM448 138.482l511.872-74.482v448h-511.872zM959.998 576l-0.126 448-511.872-72.016v-375.984zM384 943.836l-383.688-52.594-0.020-315.242h383.708z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["windows8","brand","os"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":24,"code":59712,"name":"microsoft"},"setIdx":2,"setId":1,"iconIdx":24},{"icon":{"paths":["M128 128h320v768h-320zM576 128h320v768h-320z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["pause","player"],"grid":16},"attrs":[{}],"properties":{"order":2,"id":1,"prevSize":24,"code":59695,"name":"pause"},"setIdx":2,"setId":1,"iconIdx":25},{"icon":{"paths":["M192 128l640 384-640 384z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["play","player"],"grid":16},"attrs":[{}],"properties":{"order":3,"id":0,"prevSize":24,"code":59696,"name":"play"},"setIdx":2,"setId":1,"iconIdx":26},{"icon":{"paths":["M889.68 166.32c-93.608-102.216-228.154-166.32-377.68-166.32-282.77 0-512 229.23-512 512h96c0-229.75 186.25-416 416-416 123.020 0 233.542 53.418 309.696 138.306l-149.696 149.694h352v-352l-134.32 134.32z","M928 512c0 229.75-186.25 416-416 416-123.020 0-233.542-53.418-309.694-138.306l149.694-149.694h-352v352l134.32-134.32c93.608 102.216 228.154 166.32 377.68 166.32 282.77 0 512-229.23 512-512h-96z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"tags":["loop","repeat","player","reload","refresh","update","synchronize","arrows"],"grid":16},"attrs":[{},{}],"properties":{"order":49,"id":2,"prevSize":24,"code":59694,"name":"reset"},"setIdx":2,"setId":1,"iconIdx":27},{"icon":{"paths":["M933.79 610.25c-53.726-93.054-21.416-212.304 72.152-266.488l-100.626-174.292c-28.75 16.854-62.176 26.518-97.846 26.518-107.536 0-194.708-87.746-194.708-195.99h-201.258c0.266 33.41-8.074 67.282-25.958 98.252-53.724 93.056-173.156 124.702-266.862 70.758l-100.624 174.292c28.97 16.472 54.050 40.588 71.886 71.478 53.638 92.908 21.512 211.92-71.708 266.224l100.626 174.292c28.65-16.696 61.916-26.254 97.4-26.254 107.196 0 194.144 87.192 194.7 194.958h201.254c-0.086-33.074 8.272-66.57 25.966-97.218 53.636-92.906 172.776-124.594 266.414-71.012l100.626-174.29c-28.78-16.466-53.692-40.498-71.434-71.228zM512 719.332c-114.508 0-207.336-92.824-207.336-207.334 0-114.508 92.826-207.334 207.336-207.334 114.508 0 207.332 92.826 207.332 207.334-0.002 114.51-92.824 207.334-207.332 207.334z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["cog","gear","preferences","settings","generate","control","options"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":24,"code":59693,"name":"settings2"},"setIdx":2,"setId":1,"iconIdx":28},{"icon":{"paths":["M512 128c-247.424 0-448 200.576-448 448s200.576 448 448 448 448-200.576 448-448-200.576-448-448-448zM512 936c-198.824 0-360-161.178-360-360 0-198.824 161.176-360 360-360 198.822 0 360 161.176 360 360 0 198.822-161.178 360-360 360zM934.784 287.174c16.042-28.052 25.216-60.542 25.216-95.174 0-106.040-85.96-192-192-192-61.818 0-116.802 29.222-151.92 74.596 131.884 27.236 245.206 105.198 318.704 212.578v0zM407.92 74.596c-35.116-45.374-90.102-74.596-151.92-74.596-106.040 0-192 85.96-192 192 0 34.632 9.174 67.122 25.216 95.174 73.5-107.38 186.822-185.342 318.704-212.578z","M512 576v-256h-64v320h256v-64z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"tags":["alarm","time","clock"],"grid":16},"attrs":[{},{}],"properties":{"order":2,"id":1,"prevSize":24,"code":59716,"name":"timeout"},"setIdx":2,"setId":1,"iconIdx":29},{"icon":{"paths":["M768 64c105.87 0 192 86.13 192 192v192h-128v-192c0-35.29-28.71-64-64-64h-128c-35.29 0-64 28.71-64 64v192h16c26.4 0 48 21.6 48 48v480c0 26.4-21.6 48-48 48h-544c-26.4 0-48-21.6-48-48v-480c0-26.4 21.6-48 48-48h400v-192c0-105.87 86.13-192 192-192h128z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["unlocked","lock-open"],"grid":16},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":24,"code":59699,"name":"unlocked"},"setIdx":2,"setId":1,"iconIdx":30},{"icon":{"paths":["M409.6 204.8h-153.6c-28.314 0-51.2 22.886-51.2 51.2v153.6c0 28.262 22.886 51.2 51.2 51.2h153.6c28.314 0 51.2-22.938 51.2-51.2v-153.6c0-28.262-22.886-51.2-51.2-51.2zM768 204.8h-153.6c-28.314 0-51.2 22.886-51.2 51.2v153.6c0 28.262 22.886 51.2 51.2 51.2h153.6c28.314 0 51.2-22.938 51.2-51.2v-153.6c0-28.262-22.886-51.2-51.2-51.2zM409.6 563.2h-153.6c-28.314 0-51.2 22.886-51.2 51.2v153.6c0 28.262 22.886 51.2 51.2 51.2h153.6c28.314 0 51.2-22.938 51.2-51.2v-153.6c0-28.262-22.886-51.2-51.2-51.2zM768 563.2h-153.6c-28.314 0-51.2 22.886-51.2 51.2v153.6c0 28.262 22.886 51.2 51.2 51.2h153.6c28.314 0 51.2-22.938 51.2-51.2v-153.6c0-28.262-22.886-51.2-51.2-51.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["grid"],"grid":20},"attrs":[{}],"properties":{"order":1,"id":1,"prevSize":20,"code":59730,"name":"grid1"},"setIdx":2,"setId":1,"iconIdx":31},{"icon":{"paths":["M737.28 460.8h-296.96c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2h296.96c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2zM839.68 716.8h-399.36c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2h399.36c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2zM440.32 307.2h399.36c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2h-399.36c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2zM276.48 460.8h-92.16c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2h92.16c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2zM276.48 716.8h-92.16c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2h92.16c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2zM276.48 204.8h-92.16c-28.262 0-30.72 22.886-30.72 51.2s2.458 51.2 30.72 51.2h92.16c28.262 0 30.72-22.886 30.72-51.2s-2.458-51.2-30.72-51.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["list"],"grid":20},"attrs":[{}],"properties":{"order":1,"id":0,"name":"list","prevSize":20,"code":59726},"setIdx":2,"setId":1,"iconIdx":32},{"icon":{"paths":["M636.518 0c68.608 0 102.912 46.694 102.912 100.198 0 66.816-59.597 128.614-137.165 128.614-64.973 0-102.861-38.4-101.069-101.888 0-53.402 45.107-126.925 135.322-126.925zM425.421 1024c-54.17 0-93.85-33.382-55.962-180.429l62.157-260.71c10.803-41.677 12.595-58.419 0-58.419-16.23 0-86.477 28.774-128.102 57.19l-27.034-45.056c131.686-111.923 283.187-177.51 348.211-177.51 54.118 0 63.13 65.178 36.096 165.376l-71.219 274.022c-12.595 48.384-7.219 65.075 5.427 65.075 16.23 0 69.478-20.070 121.805-61.798l30.72 41.677c-128.102 130.406-268.032 180.582-322.099 180.582z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["info"],"grid":20},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":20,"code":59708,"name":"info"},"setIdx":2,"setId":1,"iconIdx":33},{"icon":{"paths":["M832 416h-320v64h-64v-96h384v-192h-32v96c0 17.664-14.336 32-32 32h-576c-17.696 0-32-14.336-32-32v-128c0-17.696 14.304-32 32-32h576c17.664 0 32 14.304 32 32h64v256h-32zM736 160h-512v32h512v-32zM544 832c0 35.328-28.672 64-64 64s-64-28.672-64-64v-320h128v320zM480 786.656c-17.696 0-32 14.336-32 32 0 17.696 14.304 32 32 32 17.664 0 32-14.304 32-32 0-17.664-14.336-32-32-32z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["paint","tool"],"grid":32},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59725,"name":"control-Color"},"setIdx":2,"setId":1,"iconIdx":34},{"icon":{"paths":["M1328 320c-8.832 0-16 7.168-16 16v640c0 8.832-7.168 16-16 16h-1248c-8.832 0-16-7.168-16-16v-640c0-8.832-7.168-16-16-16s-16 7.168-16 16v640c0 26.464 21.536 48 48 48h1248c26.464 0 48-21.536 48-48v-640c0-8.832-7.168-16-16-16zM1296 0h-1248c-26.464 0-48 21.536-48 48v192c0 8.832 7.168 16 16 16h1312c8.832 0 16-7.168 16-16v-192c0-26.464-21.536-48-48-48zM1312 224h-1280v-176c0-8.832 7.168-16 16-16h1248c8.832 0 16 7.168 16 16v176zM560 896c8.832 0 16-7.168 16-16v-512c0-8.832-7.168-16-16-16h-416c-8.832 0-16 7.168-16 16v512c0 8.832 7.168 16 16 16h416zM160 384h384v480h-384v-480zM720 480h480c8.832 0 16-7.168 16-16s-7.168-16-16-16h-480c-8.832 0-16 7.168-16 16s7.168 16 16 16zM720 640h480c8.832 0 16-7.168 16-16s-7.168-16-16-16h-480c-8.832 0-16 7.168-16 16s7.168 16 16 16zM720 800h480c8.832 0 16-7.168 16-16s-7.168-16-16-16h-480c-8.832 0-16 7.168-16 16s7.168 16 16 16zM96 128c0 17.673 14.327 32 32 32s32-14.327 32-32c0-17.673-14.327-32-32-32s-32 14.327-32 32zM224 128c0 17.673 14.327 32 32 32s32-14.327 32-32c0-17.673-14.327-32-32-32s-32 14.327-32 32zM352 128c0 17.673 14.327 32 32 32s32-14.327 32-32c0-17.673-14.327-32-32-32s-32 14.327-32 32z"],"attrs":[{}],"width":1344,"isMulticolor":false,"isMulticolor2":false,"tags":["browser","window","software","program"],"grid":32},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59701,"name":"browser"},"setIdx":2,"setId":1,"iconIdx":35},{"icon":{"paths":["M927.936 272.992l-68.288-68.288c-12.608-12.576-32.96-12.576-45.536 0l-409.44 409.44-194.752-196.16c-12.576-12.576-32.928-12.576-45.536 0l-68.288 68.288c-12.576 12.608-12.576 32.96 0 45.536l285.568 287.488c12.576 12.576 32.96 12.576 45.536 0l500.736-500.768c12.576-12.544 12.576-32.96 0-45.536z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["checkmark","tick","approve","submit"],"grid":32},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59714,"name":"checkmark"},"setIdx":2,"setId":1,"iconIdx":36},{"icon":{"paths":["M1020.192 401.824c-8.864-25.568-31.616-44.288-59.008-48.352l-266.432-39.616-115.808-240.448c-12.192-25.248-38.272-41.408-66.944-41.408s-54.752 16.16-66.944 41.408l-115.808 240.448-266.464 39.616c-27.36 4.064-50.112 22.784-58.944 48.352-8.8 25.632-2.144 53.856 17.184 73.12l195.264 194.944-45.28 270.432c-4.608 27.232 7.2 54.56 30.336 70.496 12.704 8.736 27.648 13.184 42.592 13.184 12.288 0 24.608-3.008 35.776-8.992l232.288-125.056 232.32 125.056c11.168 5.984 23.488 8.992 35.744 8.992 14.944 0 29.888-4.448 42.624-13.184 23.136-15.936 34.88-43.264 30.304-70.496l-45.312-270.432 195.328-194.944c19.296-19.296 25.92-47.52 17.184-73.12zM754.816 619.616c-16.384 16.32-23.808 39.328-20.064 61.888l45.312 270.432-232.32-124.992c-11.136-6.016-23.424-8.992-35.776-8.992-12.288 0-24.608 3.008-35.744 8.992l-232.32 124.992 45.312-270.432c3.776-22.56-3.648-45.568-20.032-61.888l-195.264-194.944 266.432-39.68c24.352-3.616 45.312-18.848 55.776-40.576l115.872-240.384 115.84 240.416c10.496 21.728 31.424 36.928 55.744 40.576l266.496 39.68-195.264 194.912z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["star","favorite"],"grid":32},"attrs":[{}],"properties":{"order":1,"id":0,"prevSize":32,"code":59706,"name":"control-Stars"},"setIdx":2,"setId":1,"iconIdx":37},{"icon":{"paths":["M66.337 575.491l276.668-171.531v-57.177l-331.627 207.614v42.189l331.627 207.614-0-57.177z","M957.663 575.49l-276.668-171.531v-57.177l331.627 207.614v42.189l-331.627 207.614 0-57.177z","M583.295 214.183l-200.825 621.623 53.007 17.527 200.837-621.623z"],"attrs":[{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["prerender"]},"attrs":[{},{},{}],"properties":{"order":114,"id":1,"name":"prerender","prevSize":32,"code":59724},"setIdx":2,"setId":1,"iconIdx":38},{"icon":{"paths":["M1024 512c0 282.77-229.23 512-512 512s-512-229.23-512-512c0-282.77 229.23-512 512-512s512 229.23 512 512z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["circle"]},"attrs":[{}],"properties":{"order":106,"id":0,"name":"circle","prevSize":32,"code":59729},"setIdx":2,"setId":1,"iconIdx":39},{"icon":{"paths":["M512 0c-15.36 0-25.6 10.24-25.6 25.6s10.24 25.6 25.6 25.6h128v870.4h-128c-15.36 0-25.6 10.24-25.6 25.6s10.24 25.6 25.6 25.6h307.2c15.36 0 25.6-10.24 25.6-25.6s-10.24-25.6-25.6-25.6h-128v-870.4h128c15.36 0 25.6-10.24 25.6-25.6s-10.24-25.6-25.6-25.6h-307.2zM51.2 204.8c-28.16 0-51.2 23.040-51.2 51.2v460.8c0 28.16 23.040 51.2 51.2 51.2h537.6v-51.2h-512c-15.36 0-25.6-10.24-25.6-25.6v-409.6c0-15.36 10.24-25.6 25.6-25.6h512v-51.2h-537.6zM742.4 204.8v51.2h204.8c15.36 0 25.6 10.24 25.6 25.6v409.6c0 15.36-10.24 25.6-25.6 25.6h-204.8v51.2h230.4c28.16 0 51.2-23.040 51.2-51.2v-460.8c0-28.16-23.040-51.2-51.2-51.2h-230.4z","M386.56 606.72c0 12.8-7.68 23.040-20.48 25.6-28.16 10.24-58.88 15.36-92.16 15.36-35.84 0-66.56-10.24-84.48-25.6s-25.6-38.4-25.6-66.56 10.24-51.2 25.6-66.56c17.92-17.92 46.080-23.040 84.48-23.040h69.12v-38.4c0-35.84-25.6-53.76-64-53.76-23.040 0-46.080 7.68-69.12 20.48-2.56 2.56-5.12 2.56-10.24 2.56-10.24 0-20.48-7.68-20.48-20.48 0-7.68 2.56-12.8 10.24-17.92 30.72-20.48 61.44-25.6 92.16-25.6 56.32 0 104.96 30.72 104.96 92.16v181.76zM345.6 501.76h-69.12c-61.44 0-69.12 28.16-69.12 53.76s7.68 56.32 69.12 56.32c23.040 0 46.080-2.56 69.12-10.24v-99.84z"],"attrs":[{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-Slug"]},"attrs":[{},{}],"properties":{"order":103,"id":0,"name":"control-Slug","prevSize":32,"code":59727},"setIdx":2,"setId":1,"iconIdx":40},{"icon":{"paths":["M295.954 822.751h-94.705c-47.353 0-88.786-41.434-88.786-88.786v-491.283c0-47.353 41.434-88.786 88.786-88.786h94.705v-59.191h-94.705c-82.867 0-147.977 65.11-147.977 147.977v491.283c0 82.867 65.11 147.977 147.977 147.977h94.705v-59.191z","M970.728 473.526c-82.867-171.653-201.249-378.821-272.277-378.821h-112.462v59.191h112.462c35.514 11.838 136.139 177.572 213.087 337.387-76.948 153.896-177.572 325.549-213.087 337.387h-112.462v59.191h112.462c71.029 0 183.491-207.168 272.277-384.74l5.919-11.838-5.919-17.757z","M266.358 337.341v260.462h59.191v-260.462z","M479.422 337.341v260.462h59.191v-260.462z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["Tags"]},"attrs":[{},{},{},{}],"properties":{"order":98,"id":0,"name":"type-Tags","prevSize":32,"code":59722},"setIdx":2,"setId":1,"iconIdx":41},{"icon":{"paths":["M512 102.4c-200.4 0-366.954 144.072-402.4 334.2-0.031 0.165-0.069 0.335-0.1 0.5-2.974 16.061-4.76 32.441-5.8 49.1-0.017 0.271-0.084 0.529-0.1 0.8 0.019 0.004 0.080-0.004 0.1 0-0.503 8.31-1.3 16.564-1.3 25 0 226.202 183.398 409.6 409.6 409.6 208.165 0 379.707-155.44 405.8-356.5 0.004-0.033-0.004-0.067 0-0.1 1.94-14.978 3.124-30.16 3.4-45.6 0.044-2.487 0.4-4.903 0.4-7.4 0-226.202-183.398-409.6-409.6-409.6zM512 153.6c185.461 0 337.902 140.924 356.4 321.5-35.181-21.812-84.232-39.9-151.6-39.9-85.35 0-140.891 41.606-194.6 81.9-49.152 36.864-95.55 71.7-163.8 71.7-86.067 0-135.862-54.67-175.9-98.6-9.001-9.901-17.11-17.483-25.4-25.3 23.131-175.603 172.981-311.3 354.9-311.3zM716.8 486.4c77.828 0 125.173 28.221 152.2 52.8-13.96 185.173-168.254 331.2-357 331.2-190.097 0-345.175-148.14-357.2-335.2 41.826 45.372 102.577 104.8 203.6 104.8 85.35 0 140.891-41.606 194.6-81.9 49.152-36.915 95.55-71.7 163.8-71.7z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["activity"]},"attrs":[{}],"properties":{"order":12,"id":36,"name":"activity, history, time","prevSize":32,"code":59652},"setIdx":2,"setId":1,"iconIdx":42},{"icon":{"paths":["M512 0c-35.392 0-64 28.608-64 64v384h-384c-35.392 0-64 28.608-64 64s28.608 64 64 64h384v384c0 35.392 28.608 64 64 64s64-28.608 64-64v-384h384c35.392 0 64-28.608 64-64s-28.608-64-64-64h-384v-384c0-35.392-28.608-64-64-64z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["add"]},"attrs":[{}],"properties":{"order":13,"id":35,"name":"add, plus","prevSize":32,"code":59653},"setIdx":2,"setId":1,"iconIdx":43},{"icon":{"paths":["M512 102.4c-226.202 0-409.6 183.398-409.6 409.6s183.398 409.6 409.6 409.6c226.202 0 409.6-183.398 409.6-409.6s-183.398-409.6-409.6-409.6zM512 153.6c197.632 0 358.4 160.819 358.4 358.4s-160.768 358.4-358.4 358.4c-197.632 0-358.4-160.819-358.4-358.4s160.768-358.4 358.4-358.4zM691.9 333c-12.893 0.002-25.782 4.882-35.5 14.6l-222.2 221.9-67.7-67.5c-19.19-19.294-51.085-19.215-70.3 0-19.15 19.15-19.15 51.050 0 70.2 0.198 0.2 26.198 26.681 52 53 12.95 13.209 25.761 26.372 35.2 36 4.719 4.814 8.607 8.755 11.2 11.4 1.296 1.322 2.293 2.281 2.9 2.9 0.279 0.282 0.488 0.486 0.6 0.6 0.001 0.001 7.591-7.429 14.6-14.3l-14.5 14.4 0.2 0.2v0.1c19.43 19.327 51.57 19.327 71 0v-0.1l258.1-257.6c19.546-19.447 19.521-51.885-0.1-71.3-9.731-9.679-22.607-14.502-35.5-14.5z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["check-circle"]},"attrs":[{}],"properties":{"order":14,"id":34,"name":"check-circle","prevSize":32,"code":59654},"setIdx":2,"setId":1,"iconIdx":44},{"icon":{"paths":["M512 1024c-282.778 0-512-229.222-512-512s229.222-512 512-512 512 229.222 512 512-229.222 512-512 512zM855.808 270.592c-19.2-19.2-50.278-19.2-69.478 0l-376.73 376.73-171.878-171.93c-19.2-19.2-50.278-19.2-69.478 0s-19.2 50.278 0 69.478c0 0 201.523 205.261 204.8 208.486 9.984 10.138 23.347 14.643 36.557 14.080 13.21 0.563 26.573-3.942 36.608-14.029 3.277-3.226 409.6-413.286 409.6-413.286 19.2-19.2 19.2-50.33 0-69.53z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["check-circle-filled"]},"attrs":[{}],"properties":{"order":27,"id":33,"name":"check-circle-filled","prevSize":32,"code":59655},"setIdx":2,"setId":1,"iconIdx":45},{"icon":{"paths":["M601.024 512l276.736 276.736c24.512 24.576 24.512 64.384 0 89.024-24.64 24.576-64.384 24.576-89.024 0l-276.736-276.736-276.736 276.736c-24.512 24.576-64.384 24.576-89.024 0-24.512-24.64-24.512-64.448 0-89.024l276.736-276.736-276.736-276.736c-24.512-24.576-24.512-64.384 0-89.024 24.64-24.576 64.512-24.576 89.024 0l276.736 276.736 276.736-276.736c24.64-24.576 64.384-24.576 89.024 0 24.512 24.64 24.512 64.448 0 89.024l-276.736 276.736z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["close"]},"attrs":[{}],"properties":{"order":28,"id":32,"name":"close","prevSize":32,"code":59656},"setIdx":2,"setId":1,"iconIdx":46},{"icon":{"paths":["M409.6 435.2h-153.6v51.2h153.6v-51.2zM409.6 332.8h-153.6v51.2h153.6v-51.2zM256 691.2h409.6v-51.2h-409.6v51.2zM409.6 230.4h-153.6v51.2h153.6v-51.2zM870.4 179.2h-51.2v-51.2c0-28.262-22.938-51.2-51.2-51.2h-614.4c-28.262 0-51.2 22.938-51.2 51.2v665.6c0 28.262 22.938 51.2 51.2 51.2h51.2v51.2c0 28.262 22.938 51.2 51.2 51.2h614.4c28.262 0 51.2-22.938 51.2-51.2v-665.6c0-28.262-22.938-51.2-51.2-51.2zM179.2 793.6c-14.157 0-25.6-11.443-25.6-25.6v-614.4c0-14.131 11.443-25.6 25.6-25.6h563.2c14.157 0 25.6 11.469 25.6 25.6v614.4c0 14.157-11.443 25.6-25.6 25.6h-563.2zM870.4 870.4c0 14.157-11.443 25.6-25.6 25.6h-563.2c-14.157 0-25.6-11.443-25.6-25.6v-25.6h512c28.262 0 51.2-22.938 51.2-51.2v-563.2h25.6c14.157 0 25.6 11.469 25.6 25.6v614.4zM614.4 230.4h-102.4c-28.262 0-51.2 22.938-51.2 51.2v153.6c0 28.262 22.938 51.2 51.2 51.2h102.4c28.262 0 51.2-22.938 51.2-51.2v-153.6c0-28.262-22.938-51.2-51.2-51.2zM614.4 435.2h-102.4v-153.6h102.4v153.6zM256 588.8h409.6v-51.2h-409.6v51.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["content"]},"attrs":[{}],"properties":{"order":37,"id":31,"name":"type-References","prevSize":32,"code":59657},"setIdx":2,"setId":1,"iconIdx":47},{"icon":{"paths":["M793.6 844.8c0 14.157-11.443 25.6-25.6 25.6h-665.6c-14.131 0-25.6-11.443-25.6-25.6v-665.6c0-14.157 11.469-25.6 25.6-25.6h665.6c14.157 0 25.6 11.443 25.6 25.6v102.4h51.2v-128c0-28.262-22.938-51.2-51.2-51.2h-716.8c-28.262 0-51.2 22.938-51.2 51.2v716.8c0 28.262 22.938 51.2 51.2 51.2h716.8c28.262 0 51.2-22.938 51.2-51.2v-281.6h-51.2v256zM991.078 237.747c-9.958-9.958-26.035-9.958-35.968 0l-391.91 391.91-238.31-238.31c-9.958-9.958-26.061-9.958-35.942 0-9.958 9.907-9.958 26.010 0 35.942l254.874 254.874c0.461 0.538 0.614 1.203 1.126 1.69 5.043 5.018 11.674 7.475 18.278 7.373 6.605 0.102 13.235-2.355 18.278-7.373 0.512-0.512 0.666-1.178 1.126-1.69l408.448-408.474c9.933-9.933 9.933-26.035 0-35.942z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-checkbox"]},"attrs":[{}],"properties":{"order":38,"id":30,"name":"control-Checkbox","prevSize":32,"code":59658},"setIdx":2,"setId":1,"iconIdx":48},{"icon":{"paths":["M51.2 0c-28.262 0-51.2 22.938-51.2 51.2v281.6c0 28.262 22.938 51.2 51.2 51.2h921.6c28.262 0 51.2-22.938 51.2-51.2v-281.6c0-28.262-22.938-51.2-51.2-51.2h-921.6zM76.8 51.2h512v281.6h-512c-14.157 0-25.6-11.443-25.6-25.6v-230.4c0-14.157 11.443-25.6 25.6-25.6zM640 51.2h307.2c14.157 0 25.6 11.443 25.6 25.6v230.4c0 14.157-11.443 25.6-25.6 25.6h-307.2v-281.6zM716.8 153.6c-0.41 0.358 89.139 102.938 89.6 102.4 0.512 0 89.6-95.36 89.6-102.4 0 0.384-172.16 0-179.2 0zM128 435.2c-42.394 0-76.8 34.406-76.8 76.8s34.406 76.8 76.8 76.8c42.394 0 76.8-34.406 76.8-76.8s-34.406-76.8-76.8-76.8zM128 486.4c14.157 0 25.6 11.443 25.6 25.6s-11.443 25.6-25.6 25.6c-14.157 0-25.6-11.443-25.6-25.6s11.443-25.6 25.6-25.6zM307.2 486.4c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h640c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-640zM128 640c-42.394 0-76.8 34.381-76.8 76.8s34.406 76.8 76.8 76.8c42.394 0 76.8-34.381 76.8-76.8s-34.406-76.8-76.8-76.8zM128 691.2c14.157 0 25.6 11.443 25.6 25.6s-11.443 25.6-25.6 25.6c-14.157 0-25.6-11.443-25.6-25.6s11.443-25.6 25.6-25.6zM307.2 691.2c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h640c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-640zM128 844.8c-42.394 0-76.8 34.381-76.8 76.8s34.406 76.8 76.8 76.8c42.394 0 76.8-34.381 76.8-76.8s-34.406-76.8-76.8-76.8zM128 896c14.157 0 25.6 11.443 25.6 25.6s-11.443 25.6-25.6 25.6c-14.157 0-25.6-11.443-25.6-25.6s11.443-25.6 25.6-25.6zM307.2 896c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h640c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-640z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-dropdown"]},"attrs":[{}],"properties":{"order":39,"id":29,"name":"control-Dropdown","prevSize":32,"code":59659},"setIdx":2,"setId":1,"iconIdx":49},{"icon":{"paths":["M512 0c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h128v870.4h-128c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h307.2c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-128v-870.4h128c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-307.2zM51.2 204.8c-28.262 0-51.2 22.938-51.2 51.2v460.8c0 28.262 22.938 51.2 51.2 51.2h537.6v-51.2h-512c-14.131 0-25.6-11.443-25.6-25.6v-409.6c0-14.157 11.469-25.6 25.6-25.6h512v-51.2h-537.6zM742.4 204.8v51.2h204.8c14.157 0 25.6 11.443 25.6 25.6v409.6c0 14.157-11.443 25.6-25.6 25.6h-204.8v51.2h230.4c28.262 0 51.2-22.938 51.2-51.2v-460.8c0-28.262-22.938-51.2-51.2-51.2h-230.4zM285.9 307c-0.589 0.051-1.161 0.048-1.75 0.15-8.243 0.051-16.396 4.474-20.85 13.050l-132.55 306.25c-6.656 12.749-2.866 28.981 8.5 36.2 11.341 7.219 25.97 2.749 32.6-10l27.65-63.85h170.5c0.512 0 0.914-0.224 1.4-0.25l27.45 64.050c6.63 12.749 21.136 17.269 32.4 10.050s15.005-23.451 8.4-36.2l-131.3-306.25c-4.454-8.576-12.432-12.973-20.65-13.050-0.614-0.102-1.211-0.099-1.8-0.15zM285.9 389.15l63.65 148.45h-127.9l64.25-148.45z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-input"]},"attrs":[{}],"properties":{"order":41,"id":28,"name":"control-Input","prevSize":32,"code":59660},"setIdx":2,"setId":1,"iconIdx":50},{"icon":{"paths":["M153.6 716.8c-84.787 0-153.6 68.813-153.6 153.6s68.813 153.6 153.6 153.6c84.787 0 153.6-68.813 153.6-153.6s-68.813-153.6-153.6-153.6zM153.6 972.8c-56.55 0-102.4-45.85-102.4-102.4s45.85-102.4 102.4-102.4c56.55 0 102.4 45.85 102.4 102.4s-45.85 102.4-102.4 102.4zM384 179.2h614.4c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-614.4c-14.131 0-25.6 11.443-25.6 25.6s11.469 25.6 25.6 25.6zM998.4 486.4h-614.4c-14.131 0-25.6 11.443-25.6 25.6s11.469 25.6 25.6 25.6h614.4c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6zM153.6 0c-84.787 0-153.6 68.787-153.6 153.6s68.813 153.6 153.6 153.6c84.787 0 153.6-68.787 153.6-153.6s-68.813-153.6-153.6-153.6zM153.6 256c-56.55 0-102.4-45.85-102.4-102.4s45.85-102.4 102.4-102.4c56.55 0 102.4 45.85 102.4 102.4s-45.85 102.4-102.4 102.4zM153.6 358.4c-84.787 0-153.6 68.787-153.6 153.6 0 84.787 68.813 153.6 153.6 153.6s153.6-68.813 153.6-153.6c0-84.813-68.813-153.6-153.6-153.6zM153.6 614.4c-56.55 0-102.4-45.85-102.4-102.4s45.85-102.4 102.4-102.4c56.55 0 102.4 45.85 102.4 102.4s-45.85 102.4-102.4 102.4zM153.6 102.4c-28.262 0-51.2 22.938-51.2 51.2s22.938 51.2 51.2 51.2c28.262 0 51.2-22.938 51.2-51.2s-22.938-51.2-51.2-51.2zM998.4 844.8h-614.4c-14.131 0-25.6 11.443-25.6 25.6s11.469 25.6 25.6 25.6h614.4c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-radio"]},"attrs":[{}],"properties":{"order":42,"id":27,"name":"control-Radio","prevSize":32,"code":59661},"setIdx":2,"setId":1,"iconIdx":51},{"icon":{"paths":["M0 0v204.8h76.8v76.8h51.2v-76.8h76.8v-204.8h-204.8zM819.2 0v204.8h204.8v-204.8h-204.8zM51.2 51.2h102.4v102.4h-102.4v-102.4zM870.4 51.2h102.4v102.4h-102.4v-102.4zM281.6 76.8v51.2h102.4v-51.2h-102.4zM486.4 76.8v51.2h102.4v-51.2h-102.4zM691.2 76.8v51.2h102.4v-51.2h-102.4zM333.25 204.8c-7.091-0.307-14.348 2.097-19.75 7.55l-74.75 74.75c-10.317 10.291-10.317 27.083 0 37.4s27.059 10.317 37.35 0l68.45-68.5h141.85v486.4h-50.7c-7.117-0.307-14.348 2.097-19.75 7.55l-23.6 23.55c-10.317 10.317-10.317 27.083 0 37.4 10.291 10.317 27.109 10.317 37.4 0l17.25-17.3h129.75l18.050 18c10.394 10.368 27.181 10.368 37.6 0 10.368-10.394 10.368-27.181 0-37.6l-24-24c-5.478-5.478-12.682-7.907-19.85-7.6h-50.95v-486.4h141.55l69.25 69.2c10.394 10.368 27.155 10.368 37.6 0 10.368-10.368 10.368-27.181 0-37.6l-75.2-75.2c-5.478-5.478-12.706-7.907-19.9-7.6h-357.65zM896 281.6v102.4h51.2v-102.4h-51.2zM76.8 384v102.4h51.2v-102.4h-51.2zM896 486.4v102.4h51.2v-102.4h-51.2zM76.8 588.8v102.4h51.2v-102.4h-51.2zM896 691.2v102.4h51.2v-102.4h-51.2zM76.8 793.6v25.6h-76.8v204.8h204.8v-76.8h76.8v-51.2h-76.8v-76.8h-76.8v-25.6h-51.2zM819.2 819.2v76.8h-25.6v51.2h25.6v76.8h204.8v-204.8h-204.8zM51.2 870.4h102.4v102.4h-102.4v-102.4zM870.4 870.4h102.4v102.4h-102.4v-102.4zM384 896v51.2h102.4v-51.2h-102.4zM588.8 896v51.2h102.4v-51.2h-102.4z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-textarea"]},"attrs":[{}],"properties":{"order":17,"id":26,"name":"control-TextArea","prevSize":32,"code":59662},"setIdx":2,"setId":1,"iconIdx":52},{"icon":{"paths":["M332.8 25.6c-127.258 0-230.4 103.142-230.4 230.4s103.142 230.4 230.4 230.4h358.4c127.258 0 230.4-103.142 230.4-230.4s-103.142-230.4-230.4-230.4h-358.4zM332.8 76.8h358.4c98.97 0 179.2 80.23 179.2 179.2s-80.23 179.2-179.2 179.2h-358.4c-98.97 0-179.2-80.23-179.2-179.2s80.23-179.2 179.2-179.2zM332.8 128c-70.707 0-128 57.293-128 128s57.293 128 128 128c70.707 0 128-57.293 128-128s-57.293-128-128-128zM332.8 179.2c42.419 0 76.8 34.381 76.8 76.8s-34.381 76.8-76.8 76.8c-42.419 0-76.8-34.381-76.8-76.8s34.381-76.8 76.8-76.8zM332.8 537.6c-127.258 0-230.4 103.142-230.4 230.4s103.142 230.4 230.4 230.4h358.4c127.258 0 230.4-103.142 230.4-230.4s-103.142-230.4-230.4-230.4h-358.4zM332.8 588.8h358.4c98.97 0 179.2 80.23 179.2 179.2s-80.23 179.2-179.2 179.2h-358.4c-98.97 0-179.2-80.23-179.2-179.2s80.23-179.2 179.2-179.2zM691.2 640c-70.707 0-128 57.293-128 128s57.293 128 128 128c70.707 0 128-57.293 128-128s-57.293-128-128-128zM691.2 691.2c42.419 0 76.8 34.381 76.8 76.8s-34.381 76.8-76.8 76.8c-42.419 0-76.8-34.381-76.8-76.8s34.381-76.8 76.8-76.8z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["control-toggle"]},"attrs":[{}],"properties":{"order":16,"id":25,"name":"control-Toggle","prevSize":32,"code":59663},"setIdx":2,"setId":1,"iconIdx":53},{"icon":{"paths":["M204.8 51.2c-56.525 0-102.4 45.875-102.4 102.4v512c0 56.525 45.875 102.4 102.4 102.4h409.6c56.525 0 102.4-45.875 102.4-102.4v-512c0-56.525-45.875-102.4-102.4-102.4h-409.6zM204.8 102.4h409.6c28.262 0 51.2 22.886 51.2 51.2v512c0 28.314-22.938 51.2-51.2 51.2h-409.6c-28.262 0-51.2-22.886-51.2-51.2v-512c0-28.314 22.938-51.2 51.2-51.2zM768 204.8v51.2c28.262 0 51.2 22.886 51.2 51.2v512c0 28.314-22.938 51.2-51.2 51.2h-409.6c-28.262 0-51.2-22.886-51.2-51.2h-51.2c0 56.525 45.875 102.4 102.4 102.4h409.6c56.525 0 102.4-45.875 102.4-102.4v-512c0-56.525-45.875-102.4-102.4-102.4z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["copy"]},"attrs":[{}],"properties":{"order":90,"id":24,"name":"copy","prevSize":32,"code":59664},"setIdx":2,"setId":1,"iconIdx":54},{"icon":{"paths":["M828.8 1024h-633.6c-105.6 0-195.2-89.6-195.2-195.2v-320c0-281.6 227.2-508.8 505.6-508.8 288 0 518.4 230.4 518.4 518.4v310.4c0 105.6-89.6 195.2-195.2 195.2zM505.6 64c-243.2 0-441.6 198.4-441.6 441.6v320c0 73.6 60.8 134.4 131.2 134.4h630.4c73.6 0 131.2-60.8 131.2-131.2v-310.4c3.2-249.6-201.6-454.4-451.2-454.4z","M512 668.8c-3.2 0-6.4 0-6.4 0-32-3.2-64-19.2-80-48l-192-278.4c-9.6-9.6-9.6-25.6-0-38.4 9.6-9.6 25.6-12.8 38.4-6.4l294.4 172.8c28.8 16 48 44.8 51.2 76.8s-6.4 64-28.8 89.6c-19.2 22.4-48 32-76.8 32zM364.8 428.8l108.8 160c6.4 9.6 19.2 19.2 32 19.2s25.6-3.2 35.2-12.8c9.6-9.6 12.8-22.4 9.6-35.2s-9.6-22.4-19.2-32l-166.4-99.2z","M678.4 364.8c-6.4 0-12.8-3.2-19.2-6.4-16-9.6-19.2-28.8-9.6-44.8l54.4-83.2c9.6-16 28.8-19.2 44.8-9.6 19.2 12.8 22.4 35.2 12.8 48l-54.4 83.2c-6.4 9.6-16 12.8-28.8 12.8z"],"attrs":[{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["dashboard"]},"attrs":[{},{},{}],"properties":{"order":26,"id":23,"name":"dashboard","prevSize":32,"code":59665},"setIdx":2,"setId":1,"iconIdx":55},{"icon":{"paths":["M597.35 819.2c14.131 0 25.6-11.469 25.6-25.6v-307.2c0-14.080-11.469-25.6-25.6-25.6s-25.6 11.52-25.6 25.6v307.2c0 14.131 11.418 25.6 25.6 25.6zM776.55 204.8h-153.6v-51.2c0-28.314-22.886-51.2-51.2-51.2h-102.4c-28.262 0-51.2 22.886-51.2 51.2v51.2h-153.6c-28.262 0-51.2 22.886-51.2 51.2v102.4c0 28.314 22.938 51.2 51.2 51.2v460.8c0 28.314 22.938 51.2 51.2 51.2h409.6c28.314 0 51.2-22.886 51.2-51.2v-460.8c28.314 0 51.2-22.886 51.2-51.2v-102.4c0-28.314-22.938-51.2-51.2-51.2zM469.35 153.6h102.4v51.2h-102.4v-51.2zM725.35 870.4h-409.6v-460.8h409.6v460.8zM776.55 358.4h-512v-102.4h512v102.4zM443.75 819.2c14.131 0 25.6-11.469 25.6-25.6v-307.2c0-14.080-11.469-25.6-25.6-25.6s-25.6 11.52-25.6 25.6v307.2c0 14.131 11.469 25.6 25.6 25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["delete"]},"attrs":[{}],"properties":{"order":29,"id":22,"name":"delete, bin","prevSize":32,"code":59666},"setIdx":2,"setId":1,"iconIdx":56},{"icon":{"paths":["M832 128h-192v-64c0-35.392-28.608-64-64-64h-128c-35.328 0-64 28.608-64 64v64h-192c-35.328 0-64 28.608-64 64v128c0 35.392 28.672 64 64 64v512c0 35.392 28.672 64 64 64h512c35.392 0 64-28.608 64-64v-512c35.392 0 64-28.608 64-64v-128c0-35.392-28.608-64-64-64zM448 64h128v64h-128v-64zM448 800c0 17.664-14.336 32-32 32s-32-14.336-32-32v-320c0-17.6 14.336-32 32-32s32 14.4 32 32v320zM640 800c0 17.664-14.336 32-32 32s-32-14.336-32-32v-320c0-17.6 14.336-32 32-32s32 14.4 32 32v320zM832 320h-640v-128h640v128z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["delete-filled"]},"attrs":[{}],"properties":{"order":36,"id":21,"name":"delete-filled","prevSize":32,"code":59667},"setIdx":2,"setId":1,"iconIdx":57},{"icon":{"paths":["M358.4 102.4c-28.314 0-51.2 22.886-51.2 51.2v256h51.2v-256h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-358.4v51.2h358.4c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-343.4zM716.8 189.8l117.4 117.4h-117.4v-117.4zM332.8 460.8c-127.232 0-230.4 103.168-230.4 230.4s103.168 230.4 230.4 230.4c127.232 0 230.4-103.168 230.4-230.4s-103.168-230.4-230.4-230.4zM332.8 512c98.816 0 179.2 80.384 179.2 179.2s-80.384 179.2-179.2 179.2c-98.816 0-179.2-80.384-179.2-179.2s80.384-179.2 179.2-179.2zM227.2 665.6c-12.39 0-22.4 10.061-22.4 22.4v6.4c0 12.39 10.010 22.4 22.4 22.4h211.2c12.39 0 22.4-10.010 22.4-22.4v-6.4c0-12.39-10.061-22.4-22.4-22.4h-211.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["document-delete"]},"attrs":[{}],"properties":{"order":35,"id":20,"name":"document-delete","prevSize":32,"code":59668},"setIdx":2,"setId":1,"iconIdx":58},{"icon":{"paths":["M358.4 102.4c-28.314 0-51.2 22.886-51.2 51.2v256h51.2v-256h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-358.4v51.2h358.4c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-343.4zM716.8 189.8l117.4 117.4h-117.4v-117.4zM332.8 460.8c-127.232 0-230.4 103.168-230.4 230.4s103.168 230.4 230.4 230.4c127.232 0 230.4-103.168 230.4-230.4s-103.168-230.4-230.4-230.4zM332.8 512c39.934 0 76.475 13.533 106.3 35.7l-250.4 249c-21.807-29.683-35.1-65.924-35.1-105.5 0-98.816 80.384-179.2 179.2-179.2zM477 585.7c21.785 29.674 35 65.947 35 105.5 0 98.816-80.384 179.2-179.2 179.2-39.906 0-76.386-13.561-106.2-35.7l250.4-249z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["document-disable"]},"attrs":[{}],"properties":{"order":40,"id":19,"name":"document-disable","prevSize":32,"code":59669},"setIdx":2,"setId":1,"iconIdx":59},{"icon":{"paths":["M358.4 102.4c-28.314 0-51.2 22.886-51.2 51.2v256h51.2v-256h307.2v153.6c0 28.314 22.886 51.2 51.2 51.2h153.6v512h-358.4v51.2h358.4c28.314 0 51.2-22.886 51.2-51.2v-548.2l-219.8-219.8h-343.4zM716.8 189.8l117.4 117.4h-117.4v-117.4zM332.8 460.8l-230.4 256v51.2h102.4v153.6h256v-153.6h102.4v-51.2l-230.4-256zM332.8 537.3l161.5 179.5h-84.7v153.6h-153.6v-153.6h-84.7l161.5-179.5z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["document-publish"]},"attrs":[{}],"properties":{"order":44,"id":18,"name":"document-publish","prevSize":32,"code":59670},"setIdx":2,"setId":1,"iconIdx":60},{"icon":{"paths":["M665.6 51.2v102.4h102.4v-102.4h-102.4zM460.8 153.6h102.4v-102.4h-102.4v102.4zM460.8 358.4h102.4v-102.4h-102.4v102.4zM665.6 358.4h102.4v-102.4h-102.4v102.4zM665.6 563.2h102.4v-102.4h-102.4v102.4zM460.8 563.2h102.4v-102.4h-102.4v102.4zM460.8 768h102.4v-102.4h-102.4v102.4zM665.6 768h102.4v-102.4h-102.4v102.4zM665.6 972.8h102.4v-102.4h-102.4v102.4zM460.8 972.8h102.4v-102.4h-102.4v102.4zM256 153.6h102.4v-102.4h-102.4v102.4zM256 358.4h102.4v-102.4h-102.4v102.4zM256 563.2h102.4v-102.4h-102.4v102.4zM256 768h102.4v-102.4h-102.4v102.4zM256 972.8h102.4v-102.4h-102.4v102.4z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["drag"]},"attrs":[{}],"properties":{"order":43,"id":17,"name":"drag","prevSize":32,"code":59671},"setIdx":2,"setId":1,"iconIdx":61},{"icon":{"paths":["M921.6 281.958c0-70.707-171.878-128.154-384-128.154s-384 57.19-384 127.898c0 10.035 3.789 19.712 10.342 29.030 0-0.051 296.858 406.067 296.858 406.067v256l153.6-51.2v-204.8c0 0 298.752-408.166 299.725-409.702 0 0 7.475-16.64 7.475-25.139zM537.6 204.8c206.899 0 318.208 53.248 332.083 76.8-13.875 23.552-125.184 76.8-332.083 76.8s-318.208-53.248-332.083-76.8c13.875-23.552 125.184-76.8 332.083-76.8zM869.376 345.856v0 0zM573.030 686.592c-6.4 8.755-9.83 19.354-9.83 30.208v167.885l-51.2 17.050v-184.934c0-10.854-3.43-21.453-9.83-30.208l-228.147-312.115c68.762 21.709 161.382 35.123 263.578 35.123 102.298 0 195.021-13.414 263.834-35.174-0.102 0.051-0.205 0.051-0.307 0.102l-228.096 312.064z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["filter"]},"attrs":[{}],"properties":{"order":18,"id":16,"name":"filter","prevSize":32,"code":59672},"setIdx":2,"setId":1,"iconIdx":62},{"icon":{"paths":["M512 0c-282.88 0-512 229.248-512 512 0 226.24 146.688 418.112 350.080 485.76 25.6 4.8 35.008-11.008 35.008-24.64 0-12.16-0.448-44.352-0.64-87.040-142.464 30.912-172.48-68.672-172.48-68.672-23.296-59.136-56.96-74.88-56.96-74.88-46.4-31.744 3.584-31.104 3.584-31.104 51.392 3.584 78.4 52.736 78.4 52.736 45.696 78.272 119.872 55.68 149.12 42.56 4.608-33.088 17.792-55.68 32.448-68.48-113.728-12.8-233.216-56.832-233.216-252.992 0-55.872 19.84-101.568 52.672-137.408-5.76-12.928-23.040-64.96 4.48-135.488 0 0 42.88-13.76 140.8 52.48 40.96-11.392 84.48-17.024 128-17.28 43.52 0.256 87.040 5.888 128 17.28 97.28-66.24 140.16-52.48 140.16-52.48 27.52 70.528 10.24 122.56 5.12 135.488 32.64 35.84 52.48 81.536 52.48 137.408 0 196.672-119.68 240-233.6 252.608 17.92 15.36 34.56 46.72 34.56 94.72 0 68.48-0.64 123.52-0.64 140.16 0 13.44 8.96 29.44 35.2 24.32 204.864-67.136 351.424-259.136 351.424-485.056 0-282.752-229.248-512-512-512z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"tags":["brand","github"],"grid":0},"attrs":[{}],"properties":{"order":77,"id":0,"name":"github","prevSize":32,"code":59713},"setIdx":2,"setId":1,"iconIdx":63},{"icon":{"paths":["M512 512h-204.8v51.2h204.8v-51.2zM768 153.6h-51.2c0-28.314-22.886-51.2-51.2-51.2h-307.2c-28.314 0-51.2 22.886-51.2 51.2h-51.2c-28.314 0-51.2 22.886-51.2 51.2v665.6c0 28.314 22.886 51.2 51.2 51.2h512c28.314 0 51.2-22.886 51.2-51.2v-665.6c0-28.314-22.886-51.2-51.2-51.2zM358.4 153.6h307.2v51.2h-307.2v-51.2zM768 819.2c0 28.314-22.886 51.2-51.2 51.2h-409.6c-28.314 0-51.2-22.886-51.2-51.2v-563.2c0-28.314 22.886-51.2 51.2-51.2 0 28.314 22.886 51.2 51.2 51.2h307.2c28.314 0 51.2-22.886 51.2-51.2 28.314 0 51.2 22.886 51.2 51.2v563.2zM307.2 460.8h409.6v-51.2h-409.6v51.2zM307.2 665.6h409.6v-51.2h-409.6v51.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["help"]},"attrs":[{}],"properties":{"order":19,"id":15,"name":"help","prevSize":32,"code":59673},"setIdx":2,"setId":1,"iconIdx":64},{"icon":{"paths":["M512 0c-169.421 0-307.2 137.779-307.2 307.2 0 78.643 15.258 164.915 45.261 256.41 23.859 72.55 56.986 148.582 98.56 226.099 70.707 131.635 140.339 220.774 143.309 224.512 4.813 6.195 12.288 9.779 20.070 9.779 7.834 0 15.258-3.584 20.122-9.779 2.97-3.686 72.602-92.826 143.309-224.512 41.574-77.517 74.701-153.549 98.56-226.099 29.952-91.494 45.21-177.766 45.21-256.41 0-169.421-137.83-307.2-307.2-307.2zM630.682 764.672c-46.234 86.374-92.979 154.982-118.682 190.822-25.6-35.635-72.038-103.885-118.221-189.952-62.874-117.146-137.779-291.738-137.779-458.342 0-141.158 114.842-256 256-256s256 114.842 256 256c0 166.298-74.65 340.582-137.318 457.472zM512 153.6c-84.685 0-153.6 68.915-153.6 153.6s68.915 153.6 153.6 153.6 153.6-68.915 153.6-153.6-68.915-153.6-153.6-153.6zM512 409.6c-56.525 0-102.4-45.875-102.4-102.4 0-56.474 45.875-102.4 102.4-102.4 56.474 0 102.4 45.926 102.4 102.4 0 56.525-45.926 102.4-102.4 102.4z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["location"]},"attrs":[{}],"properties":{"order":25,"id":13,"name":"location, control-Map, type-Geolocation","prevSize":32,"code":59675},"setIdx":2,"setId":1,"iconIdx":65},{"icon":{"paths":["M512.273 83.782c-0.141 0.056-182.959 84.073-229.418 256.782-4.481 16.584 32.696 9.296 31.036 27.527-2.034 22.136-44.668 31.201-39.109 94.764 5.659 64.734 60.321 130.141 68.527 169.673v27.655c-0.497 8.54-4.566 31.715-18.018 43.036-7.378 6.19-17.322 8.421-30.436 6.782-18.205-2.275-25.449-14.468-28.345-24.309-4.753-16.218-0.322-35.123 10.345-44 10.724-8.924 12.17-24.842 3.236-35.564-8.934-10.712-24.858-12.161-35.582-3.218-25.995 21.64-36.887 61.52-26.491 97 9.815 33.392 36.197 55.884 70.6 60.182 4.903 0.609 9.566 0.909 14 0.909 26.623 0 44.661-10.175 55.582-19.455 32.866-27.97 35.449-74.593 35.636-79.818 0.009-0.309 0.018-0.618 0.018-0.927v-21.218h0.109v-1.418c0-12.351 10.008-22.364 22.382-22.364 11.944 0 21.609 9.346 22.273 21.109v202.491c-0.206 2.912-2.536 29.892-17.891 42.945-7.368 6.274-17.384 8.53-30.545 6.873-18.214-2.275-25.476-14.468-28.364-24.291-4.762-16.228-0.322-35.151 10.345-44.018 10.724-8.933 12.188-24.833 3.255-35.564-8.924-10.694-24.876-12.161-35.6-3.218-26.013 21.631-36.887 61.52-26.491 97 9.796 33.392 36.197 55.893 70.6 60.2 4.903 0.609 9.566 0.891 14 0.891 26.623 0 44.671-10.156 55.564-19.436 32.875-27.97 35.458-74.611 35.636-79.836 0.019-0.328 0.018-0.609 0.018-0.909v-225.636l0.127-0.055v-1c0-12.595 10.219-22.8 22.836-22.8 12.349 0 22.333 9.824 22.727 22.073v227.418c0 0.309-0 0.591 0.018 0.909 0.187 5.216 2.779 51.866 35.655 79.836 10.912 9.28 28.959 19.436 55.582 19.436 4.443 0 9.088-0.282 13.982-0.891 34.394-4.307 60.804-26.818 70.6-60.2 10.405-35.48-0.487-75.36-26.491-97-10.743-8.943-26.676-7.466-35.6 3.218-8.934 10.74-7.488 26.63 3.236 35.564 10.668 8.868 15.135 27.79 10.364 44.018-2.878 9.823-10.159 22.015-28.364 24.291-13.105 1.648-23.050-0.592-30.418-6.782-13.508-11.358-17.558-34.657-18.036-43v-201.818c0.297-12.093 10.14-21.818 22.327-21.818 12.374 0 22.4 10.003 22.4 22.364v1.418h0.073v21.218c0 0.318-0 0.628 0.018 0.927 0.178 5.216 2.779 51.848 35.655 79.818 10.912 9.28 28.941 19.455 55.564 19.455 4.434 0 9.107-0.292 14-0.891 34.394-4.298 60.786-26.818 70.582-60.2 10.405-35.48-0.487-75.351-26.491-97-10.743-8.933-26.667-7.476-35.582 3.236-8.943 10.722-7.488 26.622 3.236 35.545 10.668 8.877 15.117 27.8 10.345 44.018-2.878 9.842-10.159 22.025-28.364 24.291-13.086 1.648-23.050-0.583-30.418-6.764-13.508-11.368-17.549-34.675-18.018-43v-21.018c5.305-54.103 63.095-107.777 69.091-176.364 5.531-63.563-37.121-72.627-39.145-94.764-1.669-18.232 35.498-10.944 31.036-27.527-46.468-172.709-229.269-256.726-229.4-256.782z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["logo"]},"attrs":[{}],"properties":{"order":31,"id":12,"name":"logo","prevSize":32,"code":59676},"setIdx":2,"setId":1,"iconIdx":66},{"icon":{"paths":["M947.2 0h-870.4c-42.342 0-76.8 34.458-76.8 76.8v870.4c0 42.342 34.458 76.8 76.8 76.8h870.4c42.342 0 76.8-34.458 76.8-76.8v-870.4c0-42.342-34.458-76.8-76.8-76.8zM972.8 947.2c0 14.157-11.443 25.6-25.6 25.6h-870.4c-14.131 0-25.6-11.443-25.6-25.6v-870.4c0-14.131 11.469-25.6 25.6-25.6h870.4c14.157 0 25.6 11.469 25.6 25.6v870.4zM665.6 460.8c56.448 0 102.4-45.926 102.4-102.4s-45.952-102.4-102.4-102.4c-56.448 0-102.4 45.926-102.4 102.4s45.952 102.4 102.4 102.4zM665.6 307.2c28.211 0 51.2 22.989 51.2 51.2s-22.989 51.2-51.2 51.2c-28.211 0-51.2-22.989-51.2-51.2s22.989-51.2 51.2-51.2zM896 102.4h-768c-14.131 0-25.6 11.469-25.6 25.6v614.4c0 14.157 11.469 25.6 25.6 25.6h768c14.157 0 25.6-11.443 25.6-25.6v-614.4c0-14.131-11.443-25.6-25.6-25.6zM153.6 716.8v-118.246l164.301-184.858c4.198-4.787 9.728-7.373 15.462-7.475 5.734-0.051 11.29 2.458 15.642 7.040l283.238 303.539h-478.643zM870.4 716.8h-168.090l-315.853-338.432c-14.285-15.334-33.331-23.603-53.709-23.347-20.326 0.256-39.219 9.011-53.094 24.627l-126.054 141.798v-367.846h716.8v563.2z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["media"]},"attrs":[{}],"properties":{"order":30,"id":11,"name":"media, type-Assets, trigger-AssetChanged","prevSize":32,"code":59677},"setIdx":2,"setId":1,"iconIdx":67},{"icon":{"paths":["M128 384c-70.656 0-128 57.344-128 128s57.344 128 128 128c70.656 0 128-57.344 128-128s-57.344-128-128-128zM512 384c-70.656 0-128 57.344-128 128s57.344 128 128 128c70.656 0 128-57.344 128-128s-57.344-128-128-128zM896 384c-70.656 0-128 57.344-128 128s57.344 128 128 128c70.656 0 128-57.344 128-128s-57.344-128-128-128z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["more"]},"attrs":[{}],"properties":{"order":34,"id":10,"name":"more, dots","prevSize":32,"code":59678},"setIdx":2,"setId":1,"iconIdx":68},{"icon":{"paths":["M877.12 311.104l-66.304 66.368-228.224-228.224 66.368-66.368c25.216-25.152 66.048-25.152 91.264 0l136.896 137.024c25.216 25.216 25.216 65.984 0 91.2zM760.896 427.392l-386.176 386.112c-25.216 25.28-66.048 25.28-91.264 0l-136.96-136.896c-25.216-25.28-25.216-66.112 0-91.264l386.24-386.24 228.16 228.288zM64 896v-191.872l191.936 191.872h-191.936z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["pencil"]},"attrs":[{}],"properties":{"order":47,"id":9,"name":"pencil","prevSize":32,"code":59679},"setIdx":2,"setId":1,"iconIdx":69},{"icon":{"paths":["M892.083 131.917c-73.523-73.498-193.152-73.498-266.65 0l-157.184 157.107c-9.958 10.035-9.958 26.214 0 36.275 10.061 9.984 26.24 9.984 36.25 0l157.133-157.107c53.504-53.555 140.672-53.555 194.176 0 53.581 53.504 53.581 140.672 0 194.176l-186.138 186.163c-53.53 53.581-140.672 53.581-194.176 0-10.086-10.010-26.24-10.010-36.275 0-10.035 10.086-10.035 26.189 0 36.25 36.787 36.736 84.992 55.117 133.325 55.117s96.589-18.432 133.376-55.117l186.163-186.214c73.498-73.472 73.498-193.152 0-266.65zM519.45 698.726l-157.082 157.082c-53.504 53.555-140.672 53.555-194.176 0-53.581-53.504-53.581-140.672 0-194.176l186.138-186.163c53.53-53.581 140.672-53.581 194.176 0 10.086 9.984 26.189 9.984 36.275 0 10.035-10.086 10.035-26.214 0-36.25-73.549-73.498-193.203-73.498-266.701 0l-186.163 186.163c-73.498 73.574-73.498 193.203 0 266.701 36.787 36.71 85.043 55.117 133.325 55.117 48.333 0 96.538-18.406 133.325-55.117l157.133-157.133c10.010-10.010 10.010-26.189 0-36.224-10.010-9.984-26.189-9.984-36.25 0z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["reference"]},"attrs":[{}],"properties":{"order":45,"id":8,"name":"reference","prevSize":32,"code":59680},"setIdx":2,"setId":1,"iconIdx":70},{"icon":{"paths":["M800 1024h-576c-124.8 0-224-99.2-224-224v-300.8c0-124.8 99.2-224 224-224h576c124.8 0 224 99.2 224 224v300.8c0 124.8-99.2 224-224 224zM224 339.2c-89.6 0-160 70.4-160 160v300.8c0 89.6 70.4 160 160 160h576c89.6 0 160-70.4 160-160v-300.8c0-89.6-70.4-160-160-160h-576z","M828.8 201.6h-633.6c-19.2 0-32-12.8-32-32s12.8-32 32-32h630.4c19.2 0 32 12.8 32 32s-12.8 32-28.8 32z","M716.8 64h-409.6c-19.2 0-32-12.8-32-32s12.8-32 32-32h412.8c19.2 0 32 12.8 32 32s-16 32-35.2 32z","M800 416v64c0 48-38.4 83.2-83.2 83.2h-409.6c-44.8 3.2-83.2-35.2-83.2-83.2v-64h-54.4v64c0 76.8 64 140.8 140.8 140.8h406.4c76.8 0 140.8-64 140.8-140.8v-64h-57.6z"],"attrs":[{},{},{},{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["schemas"]},"attrs":[{},{},{},{}],"properties":{"order":46,"id":7,"name":"schemas","prevSize":32,"code":59681},"setIdx":2,"setId":1,"iconIdx":71},{"icon":{"paths":["M939.776 1003.776c-27.2 27.008-71.232 27.008-98.368 0l-168.96-168.96c-66.176 38.464-142.016 62.080-224 62.080-247.744 0-448.448-200.832-448.448-448.448 0-247.744 200.704-448.448 448.448-448.448 247.68 0 448.512 200.704 448.512 448.448 0 115.136-44.672 218.944-115.904 298.304l158.656 158.656c27.008 27.136 27.008 71.168 0.064 98.368zM448.448 128.128c-176.896 0-320.32 143.36-320.32 320.32s143.424 320.32 320.32 320.32c176.96 0 320.384-143.36 320.384-320.32s-143.488-320.32-320.384-320.32z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["search"]},"attrs":[{}],"properties":{"order":23,"id":6,"name":"search","prevSize":32,"code":59682},"setIdx":2,"setId":1,"iconIdx":72},{"icon":{"paths":["M1019.11 440.755c-1.946-13.747-14.438-23.398-28.16-21.888-16.947 1.843-34.253-0.589-50.048-7.091-52.25-21.504-77.261-81.459-55.757-133.709 6.605-15.846 16.947-29.85 30.208-40.602 10.803-8.653 12.698-24.294 4.352-35.354-28.902-37.99-62.797-71.706-100.838-100.045-10.701-8.090-25.805-6.451-34.662 3.661-28.8 33.254-75.546 44.262-116.198 27.546-40.704-16.742-66.099-57.498-63.206-101.453 0.845-13.338-8.755-25.19-21.99-27.008-47.002-6.605-94.797-6.605-142.054 0.077-13.722 1.946-23.398 14.387-21.862 28.211 1.843 16.896-0.614 34.202-7.168 49.997-21.504 52.25-81.408 77.21-133.632 55.706-15.821-6.502-29.85-16.947-40.602-30.157-8.653-10.752-24.32-12.698-35.379-4.301-37.99 28.851-71.68 62.694-100.045 100.762-8.090 10.701-6.451 25.83 3.635 34.637 33.28 28.902 44.288 75.597 27.546 116.301-16.742 40.653-57.498 66.048-101.427 63.155-13.363-0.845-25.19 8.755-26.982 21.99-6.63 47.002-6.63 94.822 0.102 142.080 1.946 13.696 14.387 23.322 28.16 21.811 16.896-1.818 34.202 0.691 50.022 7.168 52.224 21.53 77.21 81.459 55.706 133.734-6.502 15.795-16.947 29.773-30.157 40.525-10.803 8.73-12.698 24.346-4.352 35.354 28.877 38.042 62.822 71.731 100.813 100.122 1.741 1.357 3.661 2.355 5.606 3.2 9.933 4.045 21.709 1.536 29.082-6.938 28.826-33.178 75.571-44.262 116.275-27.52 40.653 16.742 66.048 57.498 63.13 101.453-0.819 13.338 8.755 25.165 22.067 27.059 47.002 6.579 94.72 6.554 142.029-0.102 13.645-1.971 23.347-14.464 21.811-28.237-1.843-16.947 0.691-34.253 7.194-50.048 21.504-52.25 81.459-77.21 133.658-55.68 15.795 6.528 29.85 16.947 40.55 30.157 8.704 10.803 24.346 12.698 35.405 4.326 37.99-28.902 71.654-62.746 100.096-100.813 7.987-10.675 6.4-25.805-3.712-34.662-33.254-28.826-44.288-75.571-27.546-116.224 16.742-40.73 57.498-66.099 101.453-63.232 13.338 0.922 25.139-8.678 27.008-21.965 6.554-47.002 6.502-94.771-0.128-142.003zM971.059 554.010c-56.141 5.274-105.702 41.114-127.642 94.464s-12.058 113.613 24.090 156.902c-17.69 21.478-37.453 41.318-58.854 59.315-12.749-11.213-27.392-20.352-43.238-26.854-78.259-32.282-168.243 5.197-200.499 83.584-6.502 15.718-10.291 32.563-11.29 49.536-27.853 2.56-55.859 2.637-83.61 0.077-5.274-56.090-41.114-105.677-94.464-127.616-53.35-21.99-113.613-11.981-156.928 24.064-21.504-17.69-41.318-37.453-59.29-58.88 11.213-12.723 20.352-27.392 26.906-43.136 32.205-78.387-5.274-168.294-83.584-200.55-15.821-6.502-32.589-10.342-49.613-11.366-2.534-27.853-2.586-55.859 0-83.558 56.090-5.299 105.626-41.088 127.565-94.438 21.965-53.402 12.058-113.638-24.090-156.902 17.69-21.555 37.478-41.395 58.88-59.341 12.749 11.213 27.392 20.352 43.213 26.854 78.285 32.256 168.218-5.248 200.474-83.558 6.528-15.795 10.342-32.589 11.366-49.613 27.853-2.509 55.808-2.56 83.558 0 5.299 56.090 41.139 105.6 94.49 127.59 53.35 21.939 113.638 12.006 156.902-24.090 21.504 17.741 41.293 37.453 59.29 58.854-11.213 12.8-20.352 27.392-26.854 43.213-32.256 78.31 5.248 168.294 83.507 200.499 15.846 6.502 32.691 10.342 49.638 11.392 2.56 27.853 2.611 55.808 0.077 83.558zM512 307.2c-113.101 0-204.8 91.699-204.8 204.8 0 113.126 91.699 204.826 204.8 204.826s204.8-91.699 204.8-204.826c0-113.101-91.699-204.8-204.8-204.8zM512 665.626c-84.813 0-153.6-68.813-153.6-153.626 0-84.838 68.787-153.6 153.6-153.6 84.838 0 153.6 68.762 153.6 153.6 0 84.813-68.762 153.626-153.6 153.626z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["settings"]},"attrs":[{}],"properties":{"order":22,"id":5,"name":"settings","prevSize":32,"code":59683},"setIdx":2,"setId":1,"iconIdx":73},{"icon":{"paths":["M77.005 102.605h128v332.8c0 14.131 11.418 25.6 25.6 25.6 14.106 0 25.6-11.469 25.6-25.6v-332.8h128c14.106 0 25.6-11.469 25.6-25.6 0-14.157-11.494-25.6-25.6-25.6h-307.2c-14.182 0-25.6 11.443-25.6 25.6 0 14.106 11.418 25.6 25.6 25.6zM947.405 716.979h-179.2v-102.4h179.2c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-204.8c-14.182 0-25.6 11.443-25.6 25.6v358.4c0 14.157 11.418 25.6 25.6 25.6 14.157 0 25.6-11.443 25.6-25.6v-179.2h179.2c14.157 0 25.6-11.443 25.6-25.6s-11.494-25.6-25.6-25.6zM965.094 58.47c-9.958-9.933-26.112-9.933-36.045 0l-870.605 870.579c-9.958 9.984-9.958 26.086 0 36.045 10.010 9.984 26.112 9.984 36.045 0l870.605-870.579c9.958-9.933 9.958-26.086 0-36.045z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["type-boolean"]},"attrs":[{}],"properties":{"order":21,"id":4,"name":"type-Boolean","prevSize":32,"code":59684},"setIdx":2,"setId":1,"iconIdx":74},{"icon":{"paths":["M947.2 102.4h-128v-25.6c0-14.131-11.469-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-512v-25.6c0-14.131-11.52-25.6-25.6-25.6s-25.6 11.469-25.6 25.6v25.6h-128c-42.342 0-76.8 34.458-76.8 76.8v716.8c0 42.342 34.458 76.8 76.8 76.8h870.4c42.342 0 76.8-34.458 76.8-76.8v-716.8c0-42.342-34.458-76.8-76.8-76.8zM972.8 896c0 14.131-11.469 25.6-25.6 25.6h-870.4c-14.080 0-25.6-11.469-25.6-25.6v-537.6h921.6v537.6zM972.8 307.2h-921.6v-128c0-14.080 11.52-25.6 25.6-25.6h128v76.8c0 14.080 11.52 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h512v76.8c0 14.080 11.469 25.6 25.6 25.6s25.6-11.52 25.6-25.6v-76.8h128c14.131 0 25.6 11.52 25.6 25.6v128zM332.8 512h51.2c14.080 0 25.6-11.52 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.52-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.52-25.6 25.6s11.52 25.6 25.6 25.6zM640 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.52-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 512h51.2c14.131 0 25.6-11.52 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.52-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 614.4h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 614.4h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 614.4h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 716.8h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 716.8h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 716.8h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM179.2 819.2h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM332.8 819.2h51.2c14.080 0 25.6-11.469 25.6-25.6s-11.52-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM486.4 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.080 0-25.6 11.469-25.6 25.6s11.52 25.6 25.6 25.6zM640 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6zM793.6 819.2h51.2c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-51.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["type-datetime"]},"attrs":[{}],"properties":{"order":24,"id":3,"name":"type-DateTime","prevSize":32,"code":59685},"setIdx":2,"setId":1,"iconIdx":75},{"icon":{"paths":["M179.2 256c0-28.262 22.938-51.2 51.2-51.2h25.6c14.157 0 25.6-11.443 25.6-25.6 0-14.131-11.443-25.6-25.6-25.6h-25.6c-56.55 0-102.4 45.85-102.4 102.4v179.2c0 28.262-22.938 51.2-51.2 51.2h-25.6c-14.157 0-25.6 11.469-25.6 25.6 0 14.157 11.443 25.6 25.6 25.6h25.6c28.262 0 51.2 22.938 51.2 51.2v179.2c0 56.55 45.85 102.4 102.4 102.4h25.6c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6h-25.6c-28.262 0-51.2-22.938-51.2-51.2v-179.2c0-30.746-13.85-58.061-35.328-76.8 21.478-18.765 35.328-46.029 35.328-76.8v-179.2zM972.8 486.4h-25.6c-28.262 0-51.2-22.938-51.2-51.2v-179.2c0-56.55-45.85-102.4-102.4-102.4h-25.6c-14.157 0-25.6 11.469-25.6 25.6 0 14.157 11.443 25.6 25.6 25.6h25.6c28.262 0 51.2 22.938 51.2 51.2v179.2c0 30.771 13.85 58.035 35.328 76.8-21.478 18.739-35.328 46.054-35.328 76.8v179.2c0 28.262-22.938 51.2-51.2 51.2h-25.6c-14.157 0-25.6 11.443-25.6 25.6s11.443 25.6 25.6 25.6h25.6c56.55 0 102.4-45.85 102.4-102.4v-179.2c0-28.262 22.938-51.2 51.2-51.2h25.6c14.157 0 25.6-11.443 25.6-25.6 0-14.131-11.443-25.6-25.6-25.6zM512 332.8c-14.157 0-25.6 11.469-25.6 25.6 0 14.157 11.443 25.6 25.6 25.6s25.6-11.443 25.6-25.6c0-14.131-11.443-25.6-25.6-25.6zM512 435.2c-14.157 0-25.6 11.469-25.6 25.6v204.8c0 14.157 11.443 25.6 25.6 25.6s25.6-11.443 25.6-25.6v-204.8c0-14.131-11.443-25.6-25.6-25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["json"]},"attrs":[{}],"properties":{"order":20,"id":14,"name":"type-Json, json","prevSize":32,"code":59674},"setIdx":2,"setId":1,"iconIdx":76},{"icon":{"paths":["M256 665.6h-76.8v-332.8c0-14.131-11.469-25.6-25.6-25.6h-76.8c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h51.2v307.2h-76.8c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h204.8c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6zM614.4 307.2h-204.8c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h179.2v128h-179.2c-14.131 0-25.6 11.469-25.6 25.6v179.2c0 14.131 11.469 25.6 25.6 25.6h204.8c14.131 0 25.6-11.469 25.6-25.6s-11.469-25.6-25.6-25.6h-179.2v-128h179.2c14.131 0 25.6-11.469 25.6-25.6v-179.2c0-14.131-11.469-25.6-25.6-25.6zM972.8 307.2h-204.8c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h179.2v128h-179.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h179.2v128h-179.2c-14.131 0-25.6 11.469-25.6 25.6s11.469 25.6 25.6 25.6h204.8c14.131 0 25.6-11.469 25.6-25.6v-358.4c0-14.131-11.469-25.6-25.6-25.6z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["type-number"]},"attrs":[{}],"properties":{"order":32,"id":2,"name":"type-Number","prevSize":32,"code":59686},"setIdx":2,"setId":1,"iconIdx":77},{"icon":{"paths":["M870.4 921.6h-716.8c-14.131 0-25.6 11.443-25.6 25.6s11.469 25.6 25.6 25.6h716.8c14.157 0 25.6-11.443 25.6-25.6s-11.443-25.6-25.6-25.6zM194.688 817.152c13.030 5.555 28.083-0.461 33.613-13.44l125.030-291.712h317.338l125.005 291.712c4.173 9.677 13.568 15.488 23.526 15.488 3.405 0 6.81-0.64 10.112-2.048 13.005-5.606 18.995-20.659 13.44-33.638l-131.61-306.944c-0.051-0.051-0.051-0.154-0.102-0.205l-175.488-409.6c-4.045-9.472-13.312-15.565-23.552-15.565s-19.507 6.093-23.552 15.514l-175.488 409.6c-0.051 0.051-0.051 0.154-0.102 0.205l-131.61 306.97c-5.53 13.005 0.461 28.058 13.44 33.664zM512 141.773l136.704 319.027h-273.408l136.704-319.027z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["type-string"]},"attrs":[{}],"properties":{"order":48,"id":1,"name":"type-String","prevSize":32,"code":59687},"setIdx":2,"setId":1,"iconIdx":78},{"icon":{"paths":["M955.221 848c0-0.109 10.752 0 0 0-52.751-161.392-240.461-224-443.178-224-202.269 0-389.979 63.392-443.066 224-11.2-0.109 0-1.232 0 0 0 61.936 49.615 112 110.654 112h664.823c61.151 0 110.766-50.064 110.766-112zM290.399 288c0 123.648 99.231 336 221.645 336s221.645-212.352 221.645-336c0-123.648-99.231-224-221.645-224s-221.645 100.352-221.645 224z"],"attrs":[{}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["user"]},"attrs":[{}],"properties":{"order":33,"id":0,"name":"user","prevSize":32,"code":59688},"setIdx":2,"setId":1,"iconIdx":79}],"height":1024,"metadata":{"name":"icomoon"},"preferences":{"showGlyphs":true,"showCodes":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"icomoon"},"metrics":{"emSize":1024,"baseline":6.25,"whitespace":50},"embed":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215,"classSelector":".icon","name":"icomoon"},"historySize":100,"gridSize":16}} \ No newline at end of file diff --git a/src/Squidex/app/theme/icomoon/style.css b/src/Squidex/app/theme/icomoon/style.css index 76fcb8ae6..a64d962ae 100644 --- a/src/Squidex/app/theme/icomoon/style.css +++ b/src/Squidex/app/theme/icomoon/style.css @@ -1,10 +1,10 @@ @font-face { font-family: 'icomoon'; - src: url('fonts/icomoon.eot?wza1pz'); - src: url('fonts/icomoon.eot?wza1pz#iefix') format('embedded-opentype'), - url('fonts/icomoon.ttf?wza1pz') format('truetype'), - url('fonts/icomoon.woff?wza1pz') format('woff'), - url('fonts/icomoon.svg?wza1pz#icomoon') format('svg'); + src: url('fonts/icomoon.eot?vexzhv'); + src: url('fonts/icomoon.eot?vexzhv#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?vexzhv') format('truetype'), + url('fonts/icomoon.woff?vexzhv') format('woff'), + url('fonts/icomoon.svg?vexzhv#icomoon') format('svg'); font-weight: normal; font-style: normal; } @@ -24,6 +24,105 @@ -moz-osx-font-smoothing: grayscale; } +.icon-clone:before { + content: "\e96a"; +} +.icon-control-Tags:before { + content: "\e963"; +} +.icon-control-Checkboxes:before { + content: "\e962"; +} +.icon-control-Html:before { + content: "\e960"; +} +.icon-single-content:before { + content: "\e958"; +} +.icon-multiple-content:before { + content: "\e957"; +} +.icon-type-Array:before { + content: "\e956"; +} +.icon-exclamation:before { + content: "\e955"; +} +.icon-orleans:before { + content: "\e94b"; +} +.icon-document-lock:before { + content: "\e949"; +} +.icon-document-unpublish:before { + content: "\e93f"; +} +.icon-angle-down:before { + content: "\e900"; +} +.icon-angle-left:before { + content: "\e901"; +} +.icon-angle-right:before { + content: "\e931"; +} +.icon-angle-up:before { + content: "\e903"; +} +.icon-api:before { + content: "\e945"; +} +.icon-assets:before { + content: "\e948"; +} +.icon-bug:before { + content: "\e93d"; +} +.icon-caret-down:before { + content: "\e92c"; +} +.icon-caret-left:before { + content: "\e92a"; +} +.icon-caret-right:before { + content: "\e929"; +} +.icon-caret-up:before { + content: "\e92b"; +} +.icon-contents:before { + content: "\e946"; +} +.icon-trigger-ContentChanged:before { + content: "\e946"; +} +.icon-control-Date:before { + content: "\e936"; +} +.icon-control-DateTime:before { + content: "\e937"; +} +.icon-control-Markdown:before { + content: "\e938"; +} +.icon-grid:before { + content: "\f00a"; +} +.icon-list1:before { + content: "\f0c9"; +} +.icon-user-o:before { + content: "\e932"; +} +.icon-rules:before { + content: "\e947"; +} +.icon-minus-square:before { + content: "\e969"; +} +.icon-plus-square:before { + content: "\e968"; +} .icon-drag2:before { content: "\e961"; } @@ -42,6 +141,24 @@ .icon-download:before { content: "\e93e"; } +.icon-caret-bottom:before { + content: "\e96b"; +} +.icon-caret-top:before { + content: "\e96c"; +} +.icon-show:before { + content: "\e964"; +} +.icon-show-all:before { + content: "\e965"; +} +.icon-hide:before { + content: "\e966"; +} +.icon-hide-all:before { + content: "\e967"; +} .icon-spinner2:before { content: "\e959"; } @@ -276,87 +393,3 @@ .icon-user:before { content: "\e928"; } -.icon-control-Html:before { - content: "\e960"; -} -.icon-single-content:before { - content: "\e958"; -} -.icon-multiple-content:before { - content: "\e957"; -} -.icon-type-Array:before { - content: "\e956"; -} -.icon-exclamation:before { - content: "\e955"; -} -.icon-orleans:before { - content: "\e94b"; -} -.icon-document-lock:before { - content: "\e949"; -} -.icon-document-unpublish:before { - content: "\e93f"; -} -.icon-angle-down:before { - content: "\e900"; -} -.icon-angle-left:before { - content: "\e901"; -} -.icon-angle-right:before { - content: "\e931"; -} -.icon-angle-up:before { - content: "\e903"; -} -.icon-api:before { - content: "\e945"; -} -.icon-assets:before { - content: "\e948"; -} -.icon-bug:before { - content: "\e93d"; -} -.icon-caret-down:before { - content: "\e92c"; -} -.icon-caret-left:before { - content: "\e92a"; -} -.icon-caret-right:before { - content: "\e929"; -} -.icon-caret-up:before { - content: "\e92b"; -} -.icon-contents:before { - content: "\e946"; -} -.icon-trigger-ContentChanged:before { - content: "\e946"; -} -.icon-control-Date:before { - content: "\e936"; -} -.icon-control-DateTime:before { - content: "\e937"; -} -.icon-control-Markdown:before { - content: "\e938"; -} -.icon-grid:before { - content: "\f00a"; -} -.icon-list1:before { - content: "\f0c9"; -} -.icon-user-o:before { - content: "\e932"; -} -.icon-rules:before { - content: "\e947"; -} diff --git a/src/Squidex/appsettings.json b/src/Squidex/appsettings.json index 2907858cd..0b1d8de1a 100644 --- a/src/Squidex/appsettings.json +++ b/src/Squidex/appsettings.json @@ -253,7 +253,7 @@ "identity": { /* - * Enable password auth. Set this to false if you want to disable local login, leaving only 3rd party login options. + * Enable password auth. Set this to false if you want to disable local login, leaving only 3rd party login options. */ "allowPasswordAuth": true, /* diff --git a/tests/RunCoverage.ps1 b/tests/RunCoverage.ps1 index a30fd78cb..ea27808f8 100644 --- a/tests/RunCoverage.ps1 +++ b/tests/RunCoverage.ps1 @@ -76,6 +76,6 @@ if ($all -Or $web) { -oldStyle } -&"$folderHome\.nuget\packages\ReportGenerator\4.0.4\tools\ReportGenerator.exe" ` +&"$folderHome\.nuget\packages\ReportGenerator\4.0.4\tools\net47\ReportGenerator.exe" ` -reports:"$folderWorking\$folderReports\*.xml" ` -targetdir:"$folderWorking\$folderReports\Output" \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientJsonTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientJsonTests.cs index 5a3d8cb6d..5a2ac24a7 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientJsonTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientJsonTests.cs @@ -6,8 +6,6 @@ // ========================================================================== using FluentAssertions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Xunit; @@ -15,8 +13,6 @@ namespace Squidex.Domain.Apps.Core.Model.Apps { public class AppClientJsonTests { - private readonly JsonSerializer serializer = TestData.DefaultSerializer(); - [Fact] public void Should_serialize_and_deserialize() { @@ -34,9 +30,9 @@ namespace Squidex.Domain.Apps.Core.Model.Apps clients = clients.Revoke("4"); - var appClients = JToken.FromObject(clients, serializer).ToObject(serializer); + var serialized = clients.SerializeAndDeserialize(); - appClients.Should().BeEquivalentTo(clients); + serialized.Should().BeEquivalentTo(clients); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs index 4a97de100..9f95ac4b2 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppClientsTests.cs @@ -35,13 +35,21 @@ namespace Squidex.Domain.Apps.Core.Model.Apps } [Fact] - public void Should_throw_exception_if_assigning_clients_with_same_id() + public void Should_throw_exception_if_assigning_client_with_same_id() { var clients_1 = clients_0.Add("2", "my-secret"); Assert.Throws(() => clients_1.Add("2", "my-secret")); } + [Fact] + public void Should_throw_exception_if_assigning_client_object_with_same_id() + { + var clients_1 = clients_0.Add("2", "my-secret"); + + Assert.Throws(() => clients_1.Add("2", new AppClient("my-name", "my-secret", "my-role"))); + } + [Fact] public void Should_rename_client() { diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsJsonTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsJsonTests.cs index 2f42622f7..d8f86cde3 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsJsonTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppContributorsJsonTests.cs @@ -6,8 +6,6 @@ // ========================================================================== using FluentAssertions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Xunit; @@ -15,8 +13,6 @@ namespace Squidex.Domain.Apps.Core.Model.Apps { public class AppContributorsJsonTests { - private readonly JsonSerializer serializer = TestData.DefaultSerializer(); - [Fact] public void Should_serialize_and_deserialize() { @@ -26,7 +22,7 @@ namespace Squidex.Domain.Apps.Core.Model.Apps contributors = contributors.Assign("2", Role.Editor); contributors = contributors.Assign("3", Role.Owner); - var serialized = JToken.FromObject(contributors, serializer).ToObject(serializer); + var serialized = contributors.SerializeAndDeserialize(); serialized.Should().BeEquivalentTo(contributors); } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs index 4a3739352..9ffe845bc 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPatternJsonTests.cs @@ -7,8 +7,6 @@ using System; using FluentAssertions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Xunit; @@ -16,8 +14,6 @@ namespace Squidex.Domain.Apps.Core.Model.Apps { public class AppPatternJsonTests { - private readonly JsonSerializer serializer = TestData.DefaultSerializer(); - [Fact] public void Should_serialize_and_deserialize() { @@ -35,9 +31,9 @@ namespace Squidex.Domain.Apps.Core.Model.Apps patterns = patterns.Remove(guid1); - var appPatterns = JToken.FromObject(patterns, serializer).ToObject(serializer); + var serialized = patterns.SerializeAndDeserialize(); - appPatterns.Should().BeEquivalentTo(patterns); + serialized.Should().BeEquivalentTo(patterns); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs index 4cf183ab3..35588c136 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/AppPlanTests.cs @@ -6,8 +6,6 @@ // ========================================================================== using FluentAssertions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; using Xunit; @@ -16,16 +14,14 @@ namespace Squidex.Domain.Apps.Core.Model.Apps { public class AppPlanTests { - private readonly JsonSerializer serializer = TestData.DefaultSerializer(); - [Fact] public void Should_serialize_and_deserialize() { - var sut = new AppPlan(new RefToken("user", "Me"), "free"); + var plan = new AppPlan(new RefToken("user", "Me"), "free"); - var serialized = JToken.FromObject(sut, serializer).ToObject(serializer); + var serialized = plan.SerializeAndDeserialize(); - serialized.Should().BeEquivalentTo(sut); + serialized.Should().BeEquivalentTo(plan); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs index c0e36b181..0e5f6c51d 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/LanguagesConfigJsonTests.cs @@ -7,8 +7,6 @@ using System.Linq; using FluentAssertions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure; using Xunit; @@ -17,20 +15,18 @@ namespace Squidex.Domain.Apps.Core.Model.Apps { public class LanguagesConfigJsonTests { - private readonly JsonSerializer serializer = TestData.DefaultSerializer(); - [Fact] public void Should_serialize_and_deserialize() { - var sut = LanguagesConfig.Build( + var languages = LanguagesConfig.Build( new LanguageConfig(Language.EN), new LanguageConfig(Language.DE, true, Language.EN), new LanguageConfig(Language.IT, false, Language.DE)) .MakeMaster(Language.IT); - var serialized = JToken.FromObject(sut, serializer).ToObject(serializer); + var serialized = languages.SerializeAndDeserialize(); - serialized.Should().BeEquivalentTo(sut); + serialized.Should().BeEquivalentTo(languages); Assert.Same(serialized.FirstOrDefault(x => x.Key == "it"), serialized.Master); } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs index 82b47099a..d79477f0a 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Apps/RolesJsonTests.cs @@ -6,8 +6,6 @@ // ========================================================================== using FluentAssertions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Xunit; @@ -15,16 +13,14 @@ namespace Squidex.Domain.Apps.Core.Model.Apps { public class RolesJsonTests { - private readonly JsonSerializer serializer = TestData.DefaultSerializer(); - [Fact] public void Should_serialize_and_deserialize() { var sut = Roles.CreateDefaults("my-app"); - var serialized = JToken.FromObject(sut, serializer).ToObject(serializer); + var roles = sut.SerializeAndDeserialize(); - serialized.Should().BeEquivalentTo(sut); + roles.Should().BeEquivalentTo(sut); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs index 3c0356896..fe4b91dc5 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Contents/ContentDataTests.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Contents; +using Squidex.Infrastructure.Json.Objects; using Xunit; #pragma warning disable xUnit2013 // Do not use equality check to check for collection size. @@ -23,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.Model.Contents .AddField("field2", new ContentFieldData() .AddValue("en", 2) - .AddValue("it", null)); + .AddValue("it", JsonValue.Null)); var actual = input.ToCleaned(); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/PartitioningTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/PartitioningTests.cs index b82d85bcc..f16cdf930 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/PartitioningTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/PartitioningTests.cs @@ -68,37 +68,18 @@ namespace Squidex.Domain.Apps.Core.Model [Fact] public void Should_make_correct_equal_comparisons() { - var partitioning1a = new Partitioning("partitioning1"); - var partitioning1b = new Partitioning("partitioning1"); - var partitioning2a = new Partitioning("partitioning2"); + var partitioning1_a = new Partitioning("partitioning1"); + var partitioning1_b = new Partitioning("partitioning1"); - Assert.True(partitioning1a.Equals(partitioning1b)); + var partitioning2 = new Partitioning("partitioning2"); - Assert.False(partitioning1a.Equals(partitioning2a)); - } - - [Fact] - public void Should_make_correct_object_equal_comparisons() - { - object partitioning1a = new Partitioning("partitioning1"); - object partitioning1b = new Partitioning("partitioning1"); - object partitioning2a = new Partitioning("partitioning2"); - - Assert.True(partitioning1a.Equals(partitioning1b)); - - Assert.False(partitioning1a.Equals(partitioning2a)); - } - - [Fact] - public void Should_provide_correct_hash_codes() - { - var partitioning1a = new Partitioning("partitioning1"); - var partitioning1b = new Partitioning("partitioning1"); - var partitioning2a = new Partitioning("partitioning2"); - - Assert.Equal(partitioning1a.GetHashCode(), partitioning1b.GetHashCode()); + Assert.Equal(partitioning1_a, partitioning1_b); + Assert.Equal(partitioning1_a.GetHashCode(), partitioning1_b.GetHashCode()); + Assert.True(partitioning1_a.Equals((object)partitioning1_b)); - Assert.NotEqual(partitioning1a.GetHashCode(), partitioning2a.GetHashCode()); + Assert.NotEqual(partitioning1_a, partitioning2); + Assert.NotEqual(partitioning1_a.GetHashCode(), partitioning2.GetHashCode()); + Assert.False(partitioning1_a.Equals((object)partitioning2)); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs index 5a1cd96f3..e9f155b52 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Rules/RuleTests.cs @@ -9,8 +9,6 @@ using System; using System.Collections.Generic; using System.Linq; using FluentAssertions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Triggers; using Xunit; @@ -21,8 +19,6 @@ namespace Squidex.Domain.Apps.Core.Model.Rules { public class RuleTests { - private readonly JsonSerializer serializer = TestData.DefaultSerializer(); - public static readonly List Triggers = typeof(Rule).Assembly.GetTypes() .Where(x => x.BaseType == typeof(RuleTrigger)) @@ -123,16 +119,16 @@ namespace Squidex.Domain.Apps.Core.Model.Rules { var rule_1 = rule_0.Disable(); - var appClients = JToken.FromObject(rule_1, serializer).ToObject(serializer); + var serialized = rule_1.SerializeAndDeserialize(); - appClients.Should().BeEquivalentTo(rule_1); + serialized.Should().BeEquivalentTo(rule_1); } [Theory] [MemberData(nameof(Triggers))] public void Should_freeze_triggers(RuleTrigger trigger) { - TestData.TestFreeze(trigger); + TestUtils.TestFreeze(trigger); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldRegistryTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldRegistryTests.cs index 597b54f8d..614ee6a65 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldRegistryTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/FieldRegistryTests.cs @@ -28,12 +28,12 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas return default(T); } - public override RootField CreateRootField(long id, string name, Partitioning partitioning) + public override RootField CreateRootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null) { return null; } - public override NestedField CreateNestedField(long id, string name) + public override NestedField CreateNestedField(long id, string name, IFieldSettings settings = null) { return null; } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs index ce33221b3..439600188 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaFieldTests.cs @@ -110,7 +110,7 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas [MemberData(nameof(FieldProperties))] public void Should_freeze_field_properties(FieldProperties action) { - TestData.TestFreeze(action); + TestUtils.TestFreeze(action); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs index 8dacfd80a..4f426ee04 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Model/Schemas/SchemaTests.cs @@ -9,8 +9,6 @@ using System; using System.Collections.Generic; using System.Linq; using FluentAssertions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; using Xunit; @@ -20,7 +18,6 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas { public class SchemaTests { - private readonly JsonSerializer serializer = TestData.DefaultSerializer(); private readonly Schema schema_0 = new Schema("my-schema"); [Fact] @@ -281,8 +278,8 @@ namespace Squidex.Domain.Apps.Core.Model.Schemas [Fact] public void Should_serialize_and_deserialize_schema() { - var schemaSource = TestData.MixedSchema(); - var schemaTarget = JToken.FromObject(schemaSource, serializer).ToObject(serializer); + var schemaSource = TestUtils.MixedSchema(); + var schemaTarget = schemaSource.SerializeAndDeserialize(); schemaTarget.Should().BeEquivalentTo(schemaSource); } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionFlatTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionFlatTests.cs index 54e71a9b9..0429afaab 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionFlatTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionFlatTests.cs @@ -6,11 +6,11 @@ // ========================================================================== using System.Collections.Generic; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; using Xunit; #pragma warning disable xUnit2013 // Do not use equality check to check for collection size. @@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent .AddValue("en", 2)) .AddField("field2", new ContentFieldData() - .AddValue("de", null) + .AddValue("de", JsonValue.Null) .AddValue("en", 4)) .AddField("field3", new ContentFieldData() @@ -57,10 +57,20 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent var expected = new Dictionary { - { "field1", new ContentFieldData().AddValue("de", 1).AddValue("en", 2) }, - { "field2", new ContentFieldData().AddValue("de", null).AddValue("en", 4) }, - { "field3", (JValue)6 }, - { "field4", (JValue)7 } + { + "field1", + new ContentFieldData() + .AddValue("de", 1) + .AddValue("en", 2) + }, + { + "field2", + new ContentFieldData() + .AddValue("de", JsonValue.Null) + .AddValue("en", 4) + }, + { "field3", JsonValue.Create(6) }, + { "field4", JsonValue.Create(7) } }; Assert.True(expected.EqualsDictionary(output)); @@ -77,7 +87,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent .AddValue("en", 2)) .AddField("field2", new ContentFieldData() - .AddValue("de", null) + .AddValue("de", JsonValue.Null) .AddValue("en", 4)) .AddField("field3", new ContentFieldData() @@ -91,13 +101,13 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent new LanguageConfig(Language.EN), new LanguageConfig(Language.DE, false, Language.EN)); - var output = (Dictionary)data.ToFlatLanguageModel(fallbackConfig, new List { Language.DE }); + var output = (Dictionary)data.ToFlatLanguageModel(fallbackConfig, new List { Language.DE }); - var expected = new Dictionary + var expected = new Dictionary { - { "field1", 1 }, - { "field2", 4 }, - { "field3", 6 } + { "field1", JsonValue.Create(1) }, + { "field2", JsonValue.Create(4) }, + { "field3", JsonValue.Create(6) } }; Assert.True(expected.EqualsDictionary(output)); @@ -114,7 +124,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent .AddValue("en", 2)) .AddField("field2", new ContentFieldData() - .AddValue("de", null) + .AddValue("de", JsonValue.Null) .AddValue("en", 4)) .AddField("field3", new ContentFieldData() @@ -123,13 +133,13 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent new ContentFieldData() .AddValue("it", 7)); - var output = (Dictionary)data.ToFlatLanguageModel(languagesConfig, new List { Language.DE, Language.EN }); + var output = (Dictionary)data.ToFlatLanguageModel(languagesConfig, new List { Language.DE, Language.EN }); - var expected = new Dictionary + var expected = new Dictionary { - { "field1", 1 }, - { "field2", 4 }, - { "field3", 6 } + { "field1", JsonValue.Create(1) }, + { "field2", JsonValue.Create(4) }, + { "field3", JsonValue.Create(6) } }; Assert.True(expected.EqualsDictionary(output)); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs index 2415089c4..a19acee27 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ContentConversionTests.cs @@ -5,10 +5,10 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ConvertContent @@ -174,7 +174,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent new NamedContentData() .AddField("field1", new ContentFieldData() - .AddValue("en", new JArray("hello", "loved"))) + .AddValue("en", JsonValue.Array("hello", "loved"))) .AddField("field2", new ContentFieldData() .AddValue("iv", "world")); @@ -191,7 +191,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent new NamedContentData() .AddField("field1", new ContentFieldData() - .AddValue("en", new JArray(new JObject(new JProperty("p1", "hello"))))) + .AddValue("en", JsonValue.Array(JsonValue.Object().Add("p1", "hello")))) .AddField("field2", new ContentFieldData() .AddValue("iv", "world")); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs index 58417c0c9..278e2bb3b 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/FieldConvertersTests.cs @@ -8,12 +8,12 @@ using System.Collections.Generic; using System.Linq; using FakeItEasy; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ConvertContent @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent { var input = new ContentFieldData() - .AddValue("iv", new JObject()); + .AddValue("iv", JsonValue.Object()); var actual = FieldConverters.ForValues((f, i) => Value.Unset)(input, stringInvariantField); @@ -56,9 +56,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent { var input = new ContentFieldData() - .AddValue("iv", new JObject()); + .AddValue("iv", JsonValue.Object()); - var actual = FieldConverters.ForValues(ValueConverters.EncodeJson())(input, jsonField); + var actual = FieldConverters.ForValues(ValueConverters.EncodeJson(TestUtils.DefaultSerializer))(input, jsonField); var expected = new ContentFieldData() @@ -73,20 +73,20 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent var input = new ContentFieldData() .AddValue("iv", - new JArray( - new JObject( - new JProperty("field1", 100), - new JProperty("field2", 200), - new JProperty("invalid", 300)))); + JsonValue.Array( + JsonValue.Object() + .Add("field1", 100) + .Add("field2", 200) + .Add("invalid", 300))); var actual = FieldConverters.ForNestedName2Id(ValueConverters.ExcludeHidden())(input, arrayField); var expected = new ContentFieldData() .AddValue("iv", - new JArray( - new JObject( - new JProperty("1", 100)))); + JsonValue.Array( + JsonValue.Object() + .Add("1", 100))); Assert.Equal(expected, actual); } @@ -97,20 +97,20 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent var input = new ContentFieldData() .AddValue("iv", - new JArray( - new JObject( - new JProperty("field1", 100), - new JProperty("field2", 200), - new JProperty("invalid", 300)))); + JsonValue.Array( + JsonValue.Object() + .Add("field1", 100) + .Add("field2", 200) + .Add("invalid", 300))); var actual = FieldConverters.ForNestedName2Name(ValueConverters.ExcludeHidden())(input, arrayField); var expected = new ContentFieldData() .AddValue("iv", - new JArray( - new JObject( - new JProperty("field1", 100)))); + JsonValue.Array( + JsonValue.Object() + .Add("field1", 100))); Assert.Equal(expected, actual); } @@ -121,20 +121,20 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent var input = new ContentFieldData() .AddValue("iv", - new JArray( - new JObject( - new JProperty("1", 100), - new JProperty("2", 200), - new JProperty("99", 300)))); + JsonValue.Array( + JsonValue.Object() + .Add("1", 100) + .Add("2", 200) + .Add("99", 300))); var actual = FieldConverters.ForNestedId2Id(ValueConverters.ExcludeHidden())(input, arrayField); var expected = new ContentFieldData() .AddValue("iv", - new JArray( - new JObject( - new JProperty("1", 100)))); + JsonValue.Array( + JsonValue.Object() + .Add("1", 100))); Assert.Equal(expected, actual); } @@ -145,20 +145,20 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent var input = new ContentFieldData() .AddValue("iv", - new JArray( - new JObject( - new JProperty("1", 100), - new JProperty("2", 200), - new JProperty("99", 300)))); + JsonValue.Array( + JsonValue.Object() + .Add("1", 100) + .Add("2", 200) + .Add("99", 300))); var actual = FieldConverters.ForNestedId2Name(ValueConverters.ExcludeHidden())(input, arrayField); var expected = new ContentFieldData() .AddValue("iv", - new JArray( - new JObject( - new JProperty("field1", 100)))); + JsonValue.Array( + JsonValue.Object() + .Add("field1", 100))); Assert.Equal(expected, actual); } @@ -422,11 +422,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent { var source = new ContentFieldData() - .AddValue("iv", new JArray("1", "2")); + .AddValue("iv", JsonValue.Array("1", "2")); var expected = new ContentFieldData() - .AddValue("iv", new JArray("url/to/1", "url/to/2")); + .AddValue("iv", JsonValue.Array("url/to/1", "url/to/2")); var rtesult = FieldConverters.ResolveAssetUrls(new HashSet(new[] { "1" }), assetUrlGenerator)(source, assetsField); @@ -438,11 +438,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent { var source = new ContentFieldData() - .AddValue("iv", new JArray("1", "2")); + .AddValue("iv", JsonValue.Array("1", "2")); var expected = new ContentFieldData() - .AddValue("iv", new JArray("url/to/1", "url/to/2")); + .AddValue("iv", JsonValue.Array("url/to/1", "url/to/2")); var rtesult = FieldConverters.ResolveAssetUrls(new HashSet(new[] { "*" }), assetUrlGenerator)(source, assetsField); @@ -454,11 +454,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent { var source = new ContentFieldData() - .AddValue("iv", new JArray("1", "2")); + .AddValue("iv", JsonValue.Array("1", "2")); var expected = new ContentFieldData() - .AddValue("iv", new JArray("1", "2")); + .AddValue("iv", JsonValue.Array("1", "2")); var rtesult = FieldConverters.ResolveAssetUrls(new HashSet(new[] { "2" }), assetUrlGenerator)(source, assetsField); @@ -470,11 +470,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent { var source = new ContentFieldData() - .AddValue("iv", new JArray("1", "2")); + .AddValue("iv", JsonValue.Array("1", "2")); var expected = new ContentFieldData() - .AddValue("iv", new JArray("1", "2")); + .AddValue("iv", JsonValue.Array("1", "2")); var rtesult = FieldConverters.ResolveAssetUrls(null, assetUrlGenerator)(source, assetsField); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs index e5cd27572..f1e3a82f6 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ConvertContent/ValueConvertersTests.cs @@ -5,9 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ConvertContent @@ -21,19 +21,19 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent [Fact] public void Should_encode_json_value() { - var source = new JObject(); + var source = JsonValue.Object(); - var result = ValueConverters.EncodeJson()(source, jsonField); + var result = ValueConverters.EncodeJson(TestUtils.DefaultSerializer)(source, jsonField); - Assert.Equal("e30=", result); + Assert.Equal(JsonValue.Create("e30="), result); } [Fact] public void Should_return_same_value_if_encoding_null_value() { - var source = JValue.CreateNull(); + var source = JsonValue.Null; - var result = ValueConverters.EncodeJson()(source, jsonField); + var result = ValueConverters.EncodeJson(TestUtils.DefaultSerializer)(source, jsonField); Assert.Same(source, result); } @@ -41,9 +41,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent [Fact] public void Should_return_same_value_if_encoding_non_json_field() { - var source = (JToken)"NO-JSON"; + var source = JsonValue.Create("NO-JSON"); - var result = ValueConverters.EncodeJson()(source, stringField); + var result = ValueConverters.EncodeJson(TestUtils.DefaultSerializer)(source, stringField); Assert.Same(source, result); } @@ -51,19 +51,19 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent [Fact] public void Should_decode_json_values() { - var source = "e30="; + var source = JsonValue.Create("e30="); - var result = ValueConverters.DecodeJson()(source, jsonField); + var result = ValueConverters.DecodeJson(TestUtils.DefaultSerializer)(source, jsonField); - Assert.Equal(new JObject(), result); + Assert.Equal(JsonValue.Object(), result); } [Fact] public void Should_return_same_value_if_decoding_null_value() { - var source = JValue.CreateNull(); + var source = JsonValue.Null; - var result = ValueConverters.DecodeJson()(source, jsonField); + var result = ValueConverters.DecodeJson(TestUtils.DefaultSerializer)(source, jsonField); Assert.Same(source, result); } @@ -71,9 +71,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent [Fact] public void Should_return_same_value_if_decoding_non_json_field() { - var source = JValue.CreateNull(); + var source = JsonValue.Null; - var result = ValueConverters.EncodeJson()(source, stringField); + var result = ValueConverters.EncodeJson(TestUtils.DefaultSerializer)(source, stringField); Assert.Same(source, result); } @@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent [Fact] public void Should_return_unset_if_field_hidden() { - var source = 123; + var source = JsonValue.Create(123); var result = ValueConverters.ExcludeHidden()(source, stringField.Hide()); @@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ConvertContent [Fact] public void Should_return_unset_if_field_has_wrong_type() { - var source = "invalid"; + var source = JsonValue.Create("invalid"); var result = ValueConverters.ExcludeChangedTypes()(source, numberField); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs index 8c35f5e19..fff6720c4 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/EnrichContent/ContentEnrichmentTests.cs @@ -6,13 +6,13 @@ // ========================================================================== using System; -using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.EnrichContent; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; using Xunit; #pragma warning disable xUnit2004 // Do not use equality check to test for boolean conditions @@ -53,14 +53,14 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent data.Enrich(schema, languagesConfig.ToResolver()); - Assert.Equal(456, (int)data["my-number"]["iv"]); + Assert.Equal(456, ((JsonScalar)data["my-number"]["iv"]).Value); - Assert.Equal("de-string", (string)data["my-string"]["de"]); - Assert.Equal("en-string", (string)data["my-string"]["en"]); + Assert.Equal("de-string", data["my-string"]["de"].ToString()); + Assert.Equal("en-string", data["my-string"]["en"].ToString()); - Assert.Equal(Now.ToString(), (string)data["my-datetime"]["iv"]); + Assert.Equal(Now.ToString(), data["my-datetime"]["iv"].ToString()); - Assert.True((bool)data["my-boolean"]["iv"]); + Assert.True(((JsonScalar)data["my-boolean"]["iv"]).Value); } [Fact] @@ -77,8 +77,8 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent data.Enrich(schema, languagesConfig.ToResolver()); - Assert.Equal("en-string", (string)data["my-string"]["de"]); - Assert.Equal("en-string", (string)data["my-string"]["en"]); + Assert.Equal("en-string", data["my-string"]["de"].ToString()); + Assert.Equal("en-string", data["my-string"]["en"].ToString()); } [Fact] @@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent Fields.Assets(1, "1", Partitioning.Invariant, new AssetsFieldProperties()); - Assert.Equal(new JArray(), DefaultValueFactory.CreateDefaultValue(field, Now)); + Assert.Equal(JsonValue.Array(), DefaultValueFactory.CreateDefaultValue(field, Now)); } [Fact] @@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent Fields.Boolean(1, "1", Partitioning.Invariant, new BooleanFieldProperties { DefaultValue = true }); - Assert.Equal(true, DefaultValueFactory.CreateDefaultValue(field, Now)); + Assert.Equal(JsonValue.True, DefaultValueFactory.CreateDefaultValue(field, Now)); } [Fact] @@ -108,7 +108,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent Fields.DateTime(1, "1", Partitioning.Invariant, new DateTimeFieldProperties { DefaultValue = FutureDays(15) }); - Assert.Equal(FutureDays(15).ToString(), DefaultValueFactory.CreateDefaultValue(field, Now)); + Assert.Equal(JsonValue.Create(FutureDays(15).ToString()), DefaultValueFactory.CreateDefaultValue(field, Now)); } [Fact] @@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent Fields.DateTime(1, "1", Partitioning.Invariant, new DateTimeFieldProperties { CalculatedDefaultValue = DateTimeCalculatedDefaultValue.Today }); - Assert.Equal("2017-10-12", DefaultValueFactory.CreateDefaultValue(field, Now)); + Assert.Equal(JsonValue.Create("2017-10-12"), DefaultValueFactory.CreateDefaultValue(field, Now)); } [Fact] @@ -128,7 +128,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent Fields.DateTime(1, "1", Partitioning.Invariant, new DateTimeFieldProperties { CalculatedDefaultValue = DateTimeCalculatedDefaultValue.Now }); - Assert.Equal("2017-10-12T16:30:10Z", DefaultValueFactory.CreateDefaultValue(field, Now)); + Assert.Equal(JsonValue.Create("2017-10-12T16:30:10Z"), DefaultValueFactory.CreateDefaultValue(field, Now)); } [Fact] @@ -138,7 +138,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent Fields.Json(1, "1", Partitioning.Invariant, new JsonFieldProperties()); - Assert.Equal(new JObject(), DefaultValueFactory.CreateDefaultValue(field, Now)); + Assert.Equal(JsonValue.Object(), DefaultValueFactory.CreateDefaultValue(field, Now)); } [Fact] @@ -148,7 +148,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent Fields.Geolocation(1, "1", Partitioning.Invariant, new GeolocationFieldProperties()); - Assert.Equal(JValue.CreateNull(), DefaultValueFactory.CreateDefaultValue(field, Now)); + Assert.Equal(JsonValue.Null, DefaultValueFactory.CreateDefaultValue(field, Now)); } [Fact] @@ -158,7 +158,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent Fields.Number(1, "1", Partitioning.Invariant, new NumberFieldProperties { DefaultValue = 12 }); - Assert.Equal(12, DefaultValueFactory.CreateDefaultValue(field, Now)); + Assert.Equal(JsonValue.Create(12), DefaultValueFactory.CreateDefaultValue(field, Now)); } [Fact] @@ -168,7 +168,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent Fields.References(1, "1", Partitioning.Invariant, new ReferencesFieldProperties()); - Assert.Equal(new JArray(), DefaultValueFactory.CreateDefaultValue(field, Now)); + Assert.Equal(JsonValue.Array(), DefaultValueFactory.CreateDefaultValue(field, Now)); } [Fact] @@ -178,7 +178,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent Fields.String(1, "1", Partitioning.Invariant, new StringFieldProperties { DefaultValue = "default" }); - Assert.Equal("default", DefaultValueFactory.CreateDefaultValue(field, Now)); + Assert.Equal(JsonValue.Create("default"), DefaultValueFactory.CreateDefaultValue(field, Now)); } [Fact] @@ -188,7 +188,7 @@ namespace Squidex.Domain.Apps.Core.Operations.EnrichContent Fields.Tags(1, "1", Partitioning.Invariant, new TagsFieldProperties()); - Assert.Equal(new JArray(), DefaultValueFactory.CreateDefaultValue(field, Now)); + Assert.Equal(JsonValue.Array(), DefaultValueFactory.CreateDefaultValue(field, Now)); } private static Instant FutureDays(int days) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs index 1d83fcd1f..684035e91 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs @@ -7,12 +7,12 @@ using System; using System.Linq; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; using Xunit; #pragma warning disable xUnit2013 // Do not use equality check to check for collection size. @@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds new IdContentData() .AddField(5, new ContentFieldData() - .AddValue("iv", new JArray(id1.ToString(), id2.ToString()))); + .AddValue("iv", JsonValue.Array(id1.ToString(), id2.ToString()))); var ids = input.GetReferencedIds(schema).ToArray(); @@ -66,16 +66,16 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds new IdContentData() .AddField(5, new ContentFieldData() - .AddValue("iv", new JArray(id1.ToString(), id2.ToString()))); + .AddValue("iv", JsonValue.Array(id1.ToString(), id2.ToString()))); var converter = FieldConverters.ForValues(ValueReferencesConverter.CleanReferences(new[] { id2 })); var actual = input.ConvertId2Id(schema, converter); - var cleanedValue = (JArray)actual[5]["iv"]; + var cleanedValue = (JsonArray)actual[5]["iv"]; Assert.Equal(1, cleanedValue.Count); - Assert.Equal(id1.ToString(), cleanedValue[0]); + Assert.Equal(id1.ToString(), cleanedValue[0].ToString()); } [Fact] @@ -106,7 +106,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds { var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant); - var result = sut.ExtractReferences("invalid").ToArray(); + var result = sut.ExtractReferences(JsonValue.Create("invalid")).ToArray(); Assert.Empty(result); } @@ -116,7 +116,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds { var sut = Fields.String(1, "my-string", Partitioning.Invariant); - var result = sut.ExtractReferences("invalid").ToArray(); + var result = sut.ExtractReferences(JsonValue.Create("invalid")).ToArray(); Assert.Empty(result); } @@ -126,9 +126,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds { var sut = Fields.Assets(1, "my-asset", Partitioning.Invariant); - var result = sut.CleanReferences(null, null); + var result = sut.CleanReferences(JsonValue.Null, null); - Assert.Null(result); + Assert.Equal(JsonValue.Null, result); } [Fact] @@ -170,9 +170,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds new ReferencesFieldProperties { SchemaId = schemaId })); var value = - new JArray( - new JObject( - new JProperty("my-refs", CreateValue(id1, id2)))); + JsonValue.Array( + JsonValue.Object() + .Add("my-refs", CreateValue(id1, id2))); var result = sut.ExtractReferences(value).ToArray(); @@ -199,7 +199,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds var sut = Fields.References(1, "my-refs", Partitioning.Invariant, new ReferencesFieldProperties { SchemaId = schemaId }); - var result = sut.ExtractReferences(null).ToArray(); + var result = sut.ExtractReferences(JsonValue.Null).ToArray(); Assert.Equal(new[] { schemaId }, result); } @@ -210,7 +210,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds var sut = Fields.References(1, "my-refs", Partitioning.Invariant, new ReferencesFieldProperties { SchemaId = schemaId }); - var result = sut.ExtractReferences("invalid").ToArray(); + var result = sut.ExtractReferences(JsonValue.Create("invalid")).ToArray(); Assert.Equal(new[] { schemaId }, result); } @@ -220,9 +220,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds { var sut = Fields.References(1, "my-refs", Partitioning.Invariant); - var result = sut.CleanReferences(null, null); + var result = sut.CleanReferences(JsonValue.Null, null); - Assert.Null(result); + Assert.Equal(JsonValue.Null, result); } [Fact] @@ -267,9 +267,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds Assert.Same(token, result); } - private static JToken CreateValue(params Guid[] ids) + private static IJsonValue CreateValue(params Guid[] ids) { - return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType().ToArray()); + return ids == null ? (IJsonValue)JsonValue.Null : JsonValue.Array(ids.Select(x => (object)x.ToString()).ToArray()); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs index 8feb2df02..92ab7b60d 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs @@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateEdmSchema { var languagesConfig = LanguagesConfig.Build(Language.DE, Language.EN); - var edmModel = TestData.MixedSchema().BuildEdmType(languagesConfig.ToResolver(), x => x); + var edmModel = TestUtils.MixedSchema().BuildEdmType(languagesConfig.ToResolver(), x => x); Assert.NotNull(edmModel); } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs index 8f873f290..922e4971c 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateJsonSchema/JsonSchemaTests.cs @@ -16,7 +16,7 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateJsonSchema { public class JsonSchemaTests { - private readonly Schema schema = TestData.MixedSchema(); + private readonly Schema schema = TestUtils.MixedSchema(); [Fact] public void Should_build_json_schema() diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs index 7cdb92b62..06226aa4d 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleEventFormatterTests.cs @@ -9,13 +9,12 @@ using System; using System.Collections.Generic; using System.Security.Claims; using FakeItEasy; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; using Squidex.Shared.Identity; using Squidex.Shared.Users; using Xunit; @@ -24,7 +23,6 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { public class RuleEventFormatterTests { - private readonly JsonSerializer serializer = JsonSerializer.CreateDefault(); private readonly IUser user = A.Fake(); private readonly IRuleUrlGenerator urlGenerator = A.Fake(); private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); @@ -40,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules A.CallTo(() => user.Claims) .Returns(new List { new Claim(SquidexClaimTypes.DisplayName, "me") }); - sut = new RuleEventFormatter(serializer, urlGenerator); + sut = new RuleEventFormatter(TestUtils.DefaultSerializer, urlGenerator); } [Fact] @@ -48,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { var result = sut.ToPayload(new { Value = 1 }); - Assert.True(result is JObject); + Assert.True(result is string); } [Fact] @@ -58,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules var result = sut.ToPayload(@event); - Assert.True(result is JObject); + Assert.True(result is string); } [Fact] @@ -68,7 +66,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules var result = sut.ToEnvelope(@event); - Assert.Equal("MyEventName", result["type"]); + Assert.Contains("MyEventName", result); } [Fact] @@ -197,7 +195,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new NamedContentData() .AddField("city", new ContentFieldData() - .AddValue("iv", new JArray())) + .AddValue("iv", JsonValue.Array())) }; var result = sut.Format("$CONTENT_DATA.city.de.10", @event); @@ -214,8 +212,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new NamedContentData() .AddField("city", new ContentFieldData() - .AddValue("iv", new JObject( - new JProperty("name", "Berlin")))) + .AddValue("iv", JsonValue.Object().Add("name", "Berlin"))) }; var result = sut.Format("$CONTENT_DATA.city.de.Name", @event); @@ -266,24 +263,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new NamedContentData() .AddField("city", new ContentFieldData() - .AddValue("iv", JValue.CreateNull())) - }; - - var result = sut.Format("$CONTENT_DATA.city.iv", @event); - - Assert.Equal("UNDEFINED", result); - } - - [Fact] - public void Should_return_undefined_when_undefined() - { - var @event = new EnrichedContentEvent - { - Data = - new NamedContentData() - .AddField("city", - new ContentFieldData() - .AddValue("iv", JValue.CreateUndefined())) + .AddValue("iv", JsonValue.Null)) }; var result = sut.Format("$CONTENT_DATA.city.iv", @event); @@ -300,13 +280,12 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new NamedContentData() .AddField("city", new ContentFieldData() - .AddValue("iv", new JObject( - new JProperty("name", "Berlin")))) + .AddValue("iv", JsonValue.Object().Add("name", "Berlin"))) }; var result = sut.Format("$CONTENT_DATA.city.iv", @event); - Assert.Equal(JObject.FromObject(new { name = "Berlin" }).ToString(Formatting.Indented), result); + Assert.Equal("{\"name\":\"Berlin\"}", result); } [Fact] @@ -318,7 +297,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new NamedContentData() .AddField("city", new ContentFieldData() - .AddValue("iv", new JArray( + .AddValue("iv", JsonValue.Array( "Berlin"))) }; @@ -336,8 +315,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules new NamedContentData() .AddField("city", new ContentFieldData() - .AddValue("iv", new JObject( - new JProperty("name", "Berlin")))) + .AddValue("iv", JsonValue.Object().Add("name", "Berlin"))) }; var result = sut.Format("$CONTENT_DATA.city.iv.name", @event); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs index f21de9c6b..90ce03bb3 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleServiceTests.cs @@ -8,7 +8,6 @@ using System; using System.Threading.Tasks; using FakeItEasy; -using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; @@ -30,6 +29,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules private readonly IRuleActionHandler ruleActionHandler = A.Fake(); private readonly IEventEnricher eventEnricher = A.Fake(); private readonly IClock clock = A.Fake(); + private readonly string actionData = "{\"value\":10}"; + private readonly string actionDump = "MyDump"; + private readonly string actionName = "ValidAction"; + private readonly string actionDescription = "MyDescription"; private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry(); private readonly RuleService sut; @@ -37,12 +40,18 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { } + public sealed class InvalidAction : RuleAction + { + } + public sealed class ValidAction : RuleAction { + public int Value { get; set; } } - public sealed class InvalidAction : RuleAction + public sealed class ValidData { + public int Value { get; set; } } public sealed class InvalidTrigger : RuleTrigger @@ -56,7 +65,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules public RuleServiceTests() { typeNameRegistry.Map(typeof(ContentCreated)); - typeNameRegistry.Map(typeof(ValidAction), "ValidAction"); + typeNameRegistry.Map(typeof(ValidAction), actionName); A.CallTo(() => eventEnricher.EnrichAsync(A>.Ignored)) .Returns(new EnrichedContentEvent()); @@ -64,16 +73,30 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules A.CallTo(() => ruleActionHandler.ActionType) .Returns(typeof(ValidAction)); + A.CallTo(() => ruleActionHandler.DataType) + .Returns(typeof(ValidData)); + A.CallTo(() => ruleTriggerHandler.TriggerType) .Returns(typeof(ContentChangedTrigger)); - sut = new RuleService(new[] { ruleTriggerHandler }, new[] { ruleActionHandler }, eventEnricher, clock, typeNameRegistry); + sut = new RuleService(new[] { ruleTriggerHandler }, new[] { ruleActionHandler }, eventEnricher, TestUtils.DefaultSerializer, clock, typeNameRegistry); + } + + [Fact] + public async Task Should_not_create_if_rule_disabled() + { + var ruleConfig = ValidRule().Disable(); + var ruleEnvelope = Envelope.Create(new ContentCreated()); + + var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope); + + Assert.Null(job); } [Fact] public async Task Should_not_create_job_for_invalid_event() { - var ruleConfig = new Rule(new ContentChangedTrigger(), new ValidAction()); + var ruleConfig = ValidRule(); var ruleEnvelope = Envelope.Create(new InvalidEvent()); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope); @@ -106,7 +129,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules [Fact] public async Task Should_not_create_if_not_triggered() { - var ruleConfig = new Rule(new ContentChangedTrigger(), new ValidAction()); + var ruleConfig = ValidRule(); var ruleEnvelope = Envelope.Create(new ContentCreated()); A.CallTo(() => ruleTriggerHandler.Triggers(A>.Ignored, ruleConfig.Trigger)) @@ -124,14 +147,11 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules var now = SystemClock.Instance.GetCurrentInstant(); - var ruleConfig = new Rule(new ContentChangedTrigger(), new ValidAction()); + var ruleConfig = ValidRule(); var ruleEnvelope = Envelope.Create(@event); ruleEnvelope.SetTimestamp(now.Minus(Duration.FromDays(3))); - var actionData = new JObject(); - var actionDescription = "MyDescription"; - A.CallTo(() => clock.GetCurrentInstant()) .Returns(now); @@ -151,17 +171,13 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules { var @event = new ContentCreated { SchemaId = NamedId.Of(Guid.NewGuid(), "my-schema"), AppId = NamedId.Of(Guid.NewGuid(), "my-event") }; - var now = SystemClock.Instance.GetCurrentInstant(); + var now = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds()); - var ruleConfig = new Rule(new ContentChangedTrigger(), new ValidAction()); + var ruleConfig = ValidRule(); var ruleEnvelope = Envelope.Create(@event); ruleEnvelope.SetTimestamp(now); - var actionName = "ValidAction"; - var actionData = new JObject(); - var actionDescription = "MyDescription"; - A.CallTo(() => clock.GetCurrentInstant()) .Returns(now); @@ -169,7 +185,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules .Returns(true); A.CallTo(() => ruleActionHandler.CreateJobAsync(A.Ignored, ruleConfig.Action)) - .Returns((actionDescription, actionData)); + .Returns((actionDescription, new ValidData { Value = 10 })); var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope); @@ -188,14 +204,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules [Fact] public async Task Should_return_succeeded_job_with_full_dump_when_handler_returns_no_exception() { - var ruleJob = new JObject(); - - var actionDump = "MyDump"; - - A.CallTo(() => ruleActionHandler.ExecuteJobAsync(ruleJob)) + A.CallTo(() => ruleActionHandler.ExecuteJobAsync(A.That.Matches(x => x.Value == 10))) .Returns((actionDump, null)); - var result = await sut.InvokeAsync("ValidAction", ruleJob); + var result = await sut.InvokeAsync(actionName, actionData); Assert.Equal(RuleResult.Success, result.Result); @@ -206,14 +218,10 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules [Fact] public async Task Should_return_failed_job_with_full_dump_when_handler_returns_exception() { - var ruleJob = new JObject(); - - var actionDump = "MyDump"; - - A.CallTo(() => ruleActionHandler.ExecuteJobAsync(ruleJob)) + A.CallTo(() => ruleActionHandler.ExecuteJobAsync(A.That.Matches(x => x.Value == 10))) .Returns((actionDump, new InvalidOperationException())); - var result = await sut.InvokeAsync("ValidAction", ruleJob); + var result = await sut.InvokeAsync(actionName, actionData); Assert.Equal(RuleResult.Failed, result.Result); @@ -224,47 +232,35 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules [Fact] public async Task Should_return_timedout_job_with_full_dump_when_exception_from_handler_indicates_timeout() { - var ruleJob = new JObject(); - - var actionDump = "MyDump"; - - A.CallTo(() => ruleActionHandler.ExecuteJobAsync(ruleJob)) + A.CallTo(() => ruleActionHandler.ExecuteJobAsync(A.That.Matches(x => x.Value == 10))) .Returns((actionDump, new TimeoutException())); - var result = await sut.InvokeAsync("ValidAction", ruleJob); + var result = await sut.InvokeAsync(actionName, actionData); Assert.Equal(RuleResult.Timeout, result.Result); Assert.True(result.Elapsed >= TimeSpan.Zero); Assert.True(result.Dump.StartsWith(actionDump, StringComparison.OrdinalIgnoreCase)); + Assert.True(result.Dump.IndexOf("Action timed out.", StringComparison.OrdinalIgnoreCase) >= 0); } [Fact] public async Task Should_create_exception_details_when_job_to_execute_failed() { - var ruleJob = new JObject(); var ruleError = new InvalidOperationException(); - A.CallTo(() => ruleActionHandler.ExecuteJobAsync(ruleJob)) + A.CallTo(() => ruleActionHandler.ExecuteJobAsync(A.That.Matches(x => x.Value == 10))) .Throws(ruleError); - var result = await sut.InvokeAsync("ValidAction", ruleJob); + var result = await sut.InvokeAsync(actionName, actionData); Assert.Equal((ruleError.ToString(), RuleResult.Failed, TimeSpan.Zero), result); } - [Fact] - public async Task Should_not_create_if_rule_disabled() + private static Rule ValidRule() { - var ruleConfig = new Rule(new ContentChangedTrigger(), new ValidAction()); - var ruleEnvelope = Envelope.Create(new ContentCreated()); - - ruleConfig = ruleConfig.Disable(); - - var job = await sut.CreateJobAsync(ruleConfig, ruleEnvelope); - - Assert.Null(job); + return new Rule(new ContentChangedTrigger(), new ValidAction()); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs index 6989b814c..f24fdf033 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/Triggers/ContentChangedTriggerTests.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules.Triggers; @@ -17,6 +16,7 @@ using Squidex.Domain.Apps.Events.Contents; using Squidex.Domain.Apps.Events.Rules; using Squidex.Domain.Apps.Events.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.EventSourcing; using Xunit; @@ -85,7 +85,7 @@ namespace Squidex.Domain.Apps.Core.Operations.HandleRules.Triggers { var trigger = new ContentChangedTrigger { - Schemas = ImmutableList.Create( + Schemas = ReadOnlyCollection.Create( new ContentChangedTriggerSchema { SendCreate = sendCreate == 1, diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ContentDataObjectTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ContentDataObjectTests.cs index 592e0765e..271953f3c 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ContentDataObjectTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ContentDataObjectTests.cs @@ -7,9 +7,9 @@ using Jint; using Jint.Runtime; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Scripting.ContentWrapper; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.Scripting @@ -155,13 +155,13 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new NamedContentData() .AddField("number", new ContentFieldData() - .AddValue("iv", new JArray(1.0, 2.0))); + .AddValue("iv", JsonValue.Array(1.0, 2.0))); var expected = new NamedContentData() .AddField("number", new ContentFieldData() - .AddValue("iv", new JArray(1.0, 4.0, 5.0))); + .AddValue("iv", JsonValue.Array(1.0, 4.0, 5.0))); var result = ExecuteScript(original, @"data.number.iv = [data.number.iv[0], data.number.iv[1] + 2, 5]"); @@ -175,13 +175,13 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new NamedContentData() .AddField("number", new ContentFieldData() - .AddValue("iv", new JObject(new JProperty("lat", 1.0)))); + .AddValue("iv", JsonValue.Object().Add("lat", 1.0))); var expected = new NamedContentData() .AddField("number", new ContentFieldData() - .AddValue("iv", new JObject(new JProperty("lat", 1.0), new JProperty("lon", 4.0)))); + .AddValue("iv", JsonValue.Object().Add("lat", 1.0).Add("lon", 4.0))); var result = ExecuteScript(original, @"data.number.iv = { lat: data.number.iv.lat, lon: data.number.iv.lat + 3 }"); @@ -265,7 +265,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new NamedContentData() .AddField("obj", new ContentFieldData() - .AddValue("iv", new JObject(new JProperty("readonly", 1)))); + .AddValue("iv", JsonValue.Object().Add("readonly", 1))); Assert.Throws(() => ExecuteScript(original, "data.obj.iv.invalid = 1")); Assert.Throws(() => ExecuteScript(original, "data.obj.iv.readonly = 2")); @@ -278,7 +278,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new NamedContentData() .AddField("obj", new ContentFieldData() - .AddValue("iv", new JArray())); + .AddValue("iv", JsonValue.Array())); ExecuteScript(original, "data.obj.iv[0] = 1"); } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/Tags/TagNormalizerTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/Tags/TagNormalizerTests.cs index 7f0843ee2..6dfc8dba0 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/Tags/TagNormalizerTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/Tags/TagNormalizerTests.cs @@ -9,17 +9,16 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using FakeItEasy; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Tags; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.Tags { public class TagNormalizerTests { - private static readonly JTokenEqualityComparer JTokenEqualityComparer = new JTokenEqualityComparer(); private readonly ITagService tagService = A.Fake(); private readonly Guid appId = Guid.NewGuid(); private readonly Guid schemaId = Guid.NewGuid(); @@ -56,8 +55,8 @@ namespace Squidex.Domain.Apps.Core.Operations.Tags await tagService.NormalizeAsync(appId, schemaId, schema, newData, oldData); - Assert.Equal(new JArray("id2_1", "id2_2"), newData["tags2"]["iv"], JTokenEqualityComparer); - Assert.Equal(new JArray("id4"), newData["array"]["iv"][0]["nestedTags2"], JTokenEqualityComparer); + Assert.Equal(JsonValue.Array("id2_1", "id2_2"), newData["tags2"]["iv"]); + Assert.Equal(JsonValue.Array("id4"), GetNestedTags(newData)); } [Fact] @@ -77,8 +76,8 @@ namespace Squidex.Domain.Apps.Core.Operations.Tags await tagService.NormalizeAsync(appId, schemaId, schema, newData, null); - Assert.Equal(new JArray("id2_1", "id2_2"), newData["tags2"]["iv"], JTokenEqualityComparer); - Assert.Equal(new JArray("id4"), newData["array"]["iv"][0]["nestedTags2"], JTokenEqualityComparer); + Assert.Equal(JsonValue.Array("id2_1", "id2_2"), newData["tags2"]["iv"]); + Assert.Equal(JsonValue.Array("id4"), GetNestedTags(newData)); } [Fact] @@ -98,8 +97,16 @@ namespace Squidex.Domain.Apps.Core.Operations.Tags await tagService.NormalizeAsync(appId, schemaId, schema, newData, null); - Assert.Equal(new JArray("name2_1", "name2_2"), newData["tags2"]["iv"], JTokenEqualityComparer); - Assert.Equal(new JArray("name4"), newData["array"]["iv"][0]["nestedTags2"], JTokenEqualityComparer); + Assert.Equal(JsonValue.Array("name2_1", "name2_2"), newData["tags2"]["iv"]); + Assert.Equal(JsonValue.Array("name4"), GetNestedTags(newData)); + } + + private IJsonValue GetNestedTags(NamedContentData newData) + { + var array = (JsonArray)newData["array"]["iv"]; + var arrayItem = (JsonObject)array[0]; + + return arrayItem["nestedTags2"]; } private static NamedContentData GenerateData(string prefix) @@ -107,21 +114,21 @@ namespace Squidex.Domain.Apps.Core.Operations.Tags return new NamedContentData() .AddField("tags1", new ContentFieldData() - .AddValue("iv", new JArray($"{prefix}1"))) + .AddValue("iv", JsonValue.Array($"{prefix}1"))) .AddField("tags2", new ContentFieldData() - .AddValue("iv", new JArray($"{prefix}2_1", $"{prefix}2_2"))) + .AddValue("iv", JsonValue.Array($"{prefix}2_1", $"{prefix}2_2"))) .AddField("string", new ContentFieldData() .AddValue("iv", $"{prefix}stringValue")) .AddField("array", new ContentFieldData() .AddValue("iv", - new JArray( - new JObject( - new JProperty("nestedTags1", new JArray($"{prefix}3")), - new JProperty("nestedTags2", new JArray($"{prefix}4")), - new JProperty("string", $"{prefix}nestedStringValue"))))); + JsonValue.Array( + JsonValue.Object() + .Add("nestedTags1", JsonValue.Array($"{prefix}3")) + .Add("nestedTags2", JsonValue.Array($"{prefix}4")) + .Add("string", $"{prefix}nestedStringValue")))); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs index 44ee87e03..9ce01da68 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs @@ -9,8 +9,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var sut = Field(new ArrayFieldProperties()); - await sut.ValidateAsync(CreateValue(new JObject()), errors, ValidationTestExtensions.ValidContext); + await sut.ValidateAsync(CreateValue(JsonValue.Object()), errors, ValidationTestExtensions.ValidContext); Assert.Empty(errors); } @@ -48,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_tags_are_required_and_null() + public async Task Should_add_error_if_tags_are_required_and_null() { var sut = Field(new ArrayFieldProperties { IsRequired = true }); @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_tags_are_required_and_empty() + public async Task Should_add_error_if_tags_are_required_and_empty() { var sut = Field(new ArrayFieldProperties { IsRequired = true }); @@ -70,41 +70,41 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_value_is_not_valid() + public async Task Should_add_error_if_value_is_not_valid() { var sut = Field(new ArrayFieldProperties()); - await sut.ValidateAsync("invalid", errors); + await sut.ValidateAsync(JsonValue.Create("invalid"), errors); errors.Should().BeEquivalentTo( new[] { "Not a valid value." }); } [Fact] - public async Task Should_add_errors_if_value_has_not_enough_items() + public async Task Should_add_error_if_value_has_not_enough_items() { var sut = Field(new ArrayFieldProperties { MinItems = 3 }); - await sut.ValidateAsync(CreateValue(new JObject(), new JObject()), errors); + await sut.ValidateAsync(CreateValue(JsonValue.Object(), JsonValue.Object()), errors); errors.Should().BeEquivalentTo( new[] { "Must have at least 3 item(s)." }); } [Fact] - public async Task Should_add_errors_if_value_has_too_much_items() + public async Task Should_add_error_if_value_has_too_much_items() { var sut = Field(new ArrayFieldProperties { MaxItems = 1 }); - await sut.ValidateAsync(CreateValue(new JObject(), new JObject()), errors); + await sut.ValidateAsync(CreateValue(JsonValue.Object(), JsonValue.Object()), errors); errors.Should().BeEquivalentTo( new[] { "Must have not more than 1 item(s)." }); } - private static JToken CreateValue(params JObject[] ids) + private static IJsonValue CreateValue(params JsonObject[] ids) { - return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType().ToArray()); + return ids == null ? (IJsonValue)JsonValue.Null : JsonValue.Array(ids.OfType().ToArray()); } private static RootField Field(ArrayFieldProperties properties) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs index c82f95373..28dfab297 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs @@ -7,13 +7,13 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent; +using Squidex.Infrastructure.Collections; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_assets_are_required_and_null() + public async Task Should_add_error_if_assets_are_required_and_null() { var sut = Field(new AssetsFieldProperties { IsRequired = true }); @@ -104,7 +104,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_assets_are_required_and_empty() + public async Task Should_add_error_if_assets_are_required_and_empty() { var sut = Field(new AssetsFieldProperties { IsRequired = true }); @@ -115,18 +115,18 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_value_is_not_valid() + public async Task Should_add_error_if_value_is_not_valid() { var sut = Field(new AssetsFieldProperties()); - await sut.ValidateAsync("invalid", errors); + await sut.ValidateAsync(JsonValue.Create("invalid"), errors); errors.Should().BeEquivalentTo( new[] { "Not a valid value." }); } [Fact] - public async Task Should_add_errors_if_value_has_not_enough_items() + public async Task Should_add_error_if_value_has_not_enough_items() { var sut = Field(new AssetsFieldProperties { MinItems = 3 }); @@ -137,7 +137,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_value_has_too_much_items() + public async Task Should_add_error_if_value_has_too_much_items() { var sut = Field(new AssetsFieldProperties { MaxItems = 1 }); @@ -148,7 +148,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_asset_are_not_valid() + public async Task Should_add_error_if_asset_are_not_valid() { var assetId = Guid.NewGuid(); @@ -251,7 +251,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent [Fact] public async Task Should_add_error_if_image_has_invalid_extension() { - var sut = Field(new AssetsFieldProperties { AllowedExtensions = ImmutableList.Create("mp4") }); + var sut = Field(new AssetsFieldProperties { AllowedExtensions = ReadOnlyCollection.Create("mp4") }); await sut.ValidateAsync(CreateValue(document.AssetId, image.AssetId), errors, ctx); @@ -263,9 +263,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent }); } - private static JToken CreateValue(params Guid[] ids) + private static IJsonValue CreateValue(params Guid[] ids) { - return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType().ToArray()); + return ids == null ? (IJsonValue)JsonValue.Null : JsonValue.Array(ids.Select(x => (object)x.ToString()).ToArray()); } private static RootField Field(AssetsFieldProperties properties) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs index 537f8d9a7..5d37e4e07 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/BooleanFieldTests.cs @@ -8,8 +8,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_boolean_is_required() + public async Task Should_add_error_if_boolean_is_required() { var sut = Field(new BooleanFieldProperties { IsRequired = true }); @@ -58,19 +58,19 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_value_is_not_valid() + public async Task Should_add_error_if_value_is_not_valid() { var sut = Field(new BooleanFieldProperties()); - await sut.ValidateAsync(CreateValue("Invalid"), errors); + await sut.ValidateAsync(JsonValue.Create("Invalid"), errors); errors.Should().BeEquivalentTo( new[] { "Not a valid value." }); } - private static JValue CreateValue(object v) + private static IJsonValue CreateValue(bool? v) { - return new JValue(v); + return JsonValue.Create(v); } private static RootField Field(BooleanFieldProperties properties) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs index 441810ec7..8d6893cdd 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs @@ -8,12 +8,12 @@ using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent new NamedContentData() .AddField("my-field", new ContentFieldData() - .AddValue(1000)); + .AddValue("iv", 1000)); await data.ValidateAsync(context, schema, languagesConfig.ToResolver(), errors); @@ -214,7 +214,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent new NamedContentData() .AddField("my-field", new ContentFieldData() - .AddValue(1000)); + .AddValue("iv", 1000)); await data.ValidatePartialAsync(context, schema, languagesConfig.ToResolver(), errors); @@ -329,10 +329,11 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent new NamedContentData() .AddField("my-field", new ContentFieldData() - .AddValue("iv", new JArray( - new JObject(), - new JObject(new JProperty("my-nested", 1)), - new JObject()))); + .AddValue("iv", + JsonValue.Array( + JsonValue.Object(), + JsonValue.Object().Add("my-nested", 1), + JsonValue.Object()))); await data.ValidatePartialAsync(context, schema, languagesConfig.ToResolver(), errors); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs index ac74d57ad..6ae3fd19b 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs @@ -9,9 +9,9 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -33,24 +33,24 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var sut = Field(new DateTimeFieldProperties()); - await sut.ValidateAsync(CreateValue(null), errors); + await sut.ValidateAsync(JsonValue.Null, errors); Assert.Empty(errors); } [Fact] - public async Task Should_add_errors_if_datetime_is_required() + public async Task Should_add_error_if_datetime_is_required() { var sut = Field(new DateTimeFieldProperties { IsRequired = true }); - await sut.ValidateAsync(CreateValue(null), errors); + await sut.ValidateAsync(JsonValue.Null, errors); errors.Should().BeEquivalentTo( new[] { "Field is required." }); } [Fact] - public async Task Should_add_errors_if_datetime_is_less_than_min() + public async Task Should_add_error_if_datetime_is_less_than_min() { var sut = Field(new DateTimeFieldProperties { MinValue = FutureDays(10) }); @@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_datetime_is_greater_than_max() + public async Task Should_add_error_if_datetime_is_greater_than_max() { var sut = Field(new DateTimeFieldProperties { MaxValue = FutureDays(10) }); @@ -72,22 +72,22 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_value_is_not_valid() + public async Task Should_add_error_if_value_is_not_valid() { var sut = Field(new DateTimeFieldProperties()); - await sut.ValidateAsync(CreateValue("Invalid"), errors); + await sut.ValidateAsync(JsonValue.Create("Invalid"), errors); errors.Should().BeEquivalentTo( new[] { "Not a valid value." }); } [Fact] - public async Task Should_add_errors_if_value_is_another_type() + public async Task Should_add_error_if_value_is_another_type() { var sut = Field(new DateTimeFieldProperties()); - await sut.ValidateAsync(CreateValue(123), errors); + await sut.ValidateAsync(JsonValue.Create(123), errors); errors.Should().BeEquivalentTo( new[] { "Not a valid value." }); @@ -98,9 +98,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent return Instant.FromDateTimeUtc(DateTime.UtcNow.Date.AddDays(days)); } - private static JValue CreateValue(object v) + private static IJsonValue CreateValue(Instant v) { - return v is Instant ? new JValue(v.ToString()) : new JValue(v); + return JsonValue.Create(v.ToString()); } private static RootField Field(DateTimeFieldProperties properties) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs index c19149e6b..ae3156853 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/GeolocationFieldTests.cs @@ -8,8 +8,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var sut = Field(new GeolocationFieldProperties()); - await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors); + await sut.ValidateAsync(JsonValue.Null, errors); Assert.Empty(errors); } @@ -41,77 +41,72 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var sut = Field(new GeolocationFieldProperties()); - var geolocation = new JObject( - new JProperty("latitude", 0), - new JProperty("longitude", 0)); + var geolocation = JsonValue.Object() + .Add("latitude", 0) + .Add("longitude", 0); - await sut.ValidateAsync(CreateValue(geolocation), errors); + await sut.ValidateAsync(geolocation, errors); Assert.Empty(errors); } [Fact] - public async Task Should_add_errors_if_geolocation_has_invalid_latitude() + public async Task Should_add_error_if_geolocation_has_invalid_latitude() { var sut = Field(new GeolocationFieldProperties { IsRequired = true }); - var geolocation = new JObject( - new JProperty("latitude", 200), - new JProperty("longitude", 0)); + var geolocation = JsonValue.Object() + .Add("latitude", 200) + .Add("longitude", 0); - await sut.ValidateAsync(CreateValue(geolocation), errors); + await sut.ValidateAsync(geolocation, errors); errors.Should().BeEquivalentTo( new[] { "Not a valid value." }); } [Fact] - public async Task Should_add_errors_if_geolocation_has_invalid_longitude() + public async Task Should_add_error_if_geolocation_has_invalid_longitude() { var sut = Field(new GeolocationFieldProperties { IsRequired = true }); - var geolocation = new JObject( - new JProperty("latitude", 0), - new JProperty("longitude", 200)); + var geolocation = JsonValue.Object() + .Add("latitude", 0) + .Add("longitude", 200); - await sut.ValidateAsync(CreateValue(geolocation), errors); + await sut.ValidateAsync(geolocation, errors); errors.Should().BeEquivalentTo( new[] { "Not a valid value." }); } [Fact] - public async Task Should_add_errors_if_geolocation_has_too_many_properties() + public async Task Should_add_error_if_geolocation_has_too_many_properties() { var sut = Field(new GeolocationFieldProperties { IsRequired = true }); - var geolocation = new JObject( - new JProperty("invalid", 0), - new JProperty("latitude", 0), - new JProperty("longitude", 0)); + var geolocation = JsonValue.Object() + .Add("invalid", 0) + .Add("latitude", 0) + .Add("longitude", 0); - await sut.ValidateAsync(CreateValue(geolocation), errors); + await sut.ValidateAsync(geolocation, errors); errors.Should().BeEquivalentTo( new[] { "Not a valid value." }); } [Fact] - public async Task Should_add_errors_if_geolocation_is_required() + public async Task Should_add_error_if_geolocation_is_required() { var sut = Field(new GeolocationFieldProperties { IsRequired = true }); - await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors); + await sut.ValidateAsync(JsonValue.Null, errors); errors.Should().BeEquivalentTo( new[] { "Field is required." }); } - private static JToken CreateValue(JToken v) - { - return v; - } - private static RootField Field(GeolocationFieldProperties properties) { return Fields.Geolocation(1, "my-geolocation", Partitioning.Invariant, properties); diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs index b6cb06f80..886a7b494 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/JsonFieldTests.cs @@ -8,8 +8,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -31,23 +31,23 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var sut = Field(new JsonFieldProperties()); - await sut.ValidateAsync(CreateValue(new JValue(1)), errors); + await sut.ValidateAsync(CreateValue(JsonValue.Create(1)), errors); Assert.Empty(errors); } [Fact] - public async Task Should_add_errors_if_json_is_required() + public async Task Should_add_error_if_json_is_required() { var sut = Field(new JsonFieldProperties { IsRequired = true }); - await sut.ValidateAsync(CreateValue(JValue.CreateNull()), errors); + await sut.ValidateAsync(CreateValue(JsonValue.Null), errors); errors.Should().BeEquivalentTo( new[] { "Field is required." }); } - private static JValue CreateValue(JValue v) + private static IJsonValue CreateValue(IJsonValue v) { return v; } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs index eac18056e..393141089 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs @@ -5,12 +5,13 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Collections; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -32,24 +33,24 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var sut = Field(new NumberFieldProperties()); - await sut.ValidateAsync(CreateValue(null), errors); + await sut.ValidateAsync(JsonValue.Null, errors); Assert.Empty(errors); } [Fact] - public async Task Should_add_errors_if_number_is_required() + public async Task Should_add_error_if_number_is_required() { var sut = Field(new NumberFieldProperties { IsRequired = true }); - await sut.ValidateAsync(CreateValue(null), errors); + await sut.ValidateAsync(JsonValue.Null, errors); errors.Should().BeEquivalentTo( new[] { "Field is required." }); } [Fact] - public async Task Should_add_errors_if_number_is_less_than_min() + public async Task Should_add_error_if_number_is_less_than_min() { var sut = Field(new NumberFieldProperties { MinValue = 10 }); @@ -60,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_number_is_greater_than_max() + public async Task Should_add_error_if_number_is_greater_than_max() { var sut = Field(new NumberFieldProperties { MaxValue = 10 }); @@ -71,9 +72,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_number_is_not_allowed() + public async Task Should_add_error_if_number_is_not_allowed() { - var sut = Field(new NumberFieldProperties { AllowedValues = ImmutableList.Create(10d) }); + var sut = Field(new NumberFieldProperties { AllowedValues = ReadOnlyCollection.Create(10d) }); await sut.ValidateAsync(CreateValue(20), errors); @@ -82,19 +83,30 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_value_is_not_valid() + public async Task Should_add_error_if_value_is_not_valid() { var sut = Field(new NumberFieldProperties()); - await sut.ValidateAsync(CreateValue("Invalid"), errors); + await sut.ValidateAsync(JsonValue.Create("Invalid"), errors); errors.Should().BeEquivalentTo( new[] { "Not a valid value." }); } - private static JValue CreateValue(object v) + [Fact] + public async Task Should_add_error_if_unique_constraint_failed() + { + var sut = Field(new NumberFieldProperties { IsUnique = true }); + + await sut.ValidateAsync(CreateValue(12.5), errors, ValidationTestExtensions.References(Guid.NewGuid())); + + errors.Should().BeEquivalentTo( + new[] { "Another content with the same value exists." }); + } + + private static IJsonValue CreateValue(double v) { - return new JValue(v); + return JsonValue.Create(v); } private static RootField Field(NumberFieldProperties properties) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs index 768b781bc..937ce1131 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs @@ -10,8 +10,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -20,6 +20,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { private readonly List errors = new List(); private readonly Guid schemaId = Guid.NewGuid(); + private readonly Guid ref1 = Guid.NewGuid(); + private readonly Guid ref2 = Guid.NewGuid(); [Fact] public void Should_instantiate_field() @@ -34,7 +36,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var sut = Field(new ReferencesFieldProperties()); - await sut.ValidateAsync(CreateValue(Guid.NewGuid()), errors, ValidationTestExtensions.ValidContext); + await sut.ValidateAsync(CreateValue(ref1), errors, ValidationTestExtensions.References(ref1)); Assert.Empty(errors); } @@ -50,7 +52,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_references_are_required_and_null() + public async Task Should_add_error_if_references_are_required_and_null() { var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, IsRequired = true }); @@ -61,7 +63,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_references_are_required_and_empty() + public async Task Should_add_error_if_references_are_required_and_empty() { var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, IsRequired = true }); @@ -72,54 +74,52 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_value_is_not_valid() + public async Task Should_add_error_if_value_is_not_valid() { var sut = Field(new ReferencesFieldProperties()); - await sut.ValidateAsync("invalid", errors); + await sut.ValidateAsync(JsonValue.Create("invalid"), errors); errors.Should().BeEquivalentTo( new[] { "Not a valid value." }); } [Fact] - public async Task Should_add_errors_if_value_has_not_enough_items() + public async Task Should_add_error_if_value_has_not_enough_items() { var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, MinItems = 3 }); - await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); + await sut.ValidateAsync(CreateValue(ref1, ref2), errors, ValidationTestExtensions.References(ref1, ref2)); errors.Should().BeEquivalentTo( new[] { "Must have at least 3 item(s)." }); } [Fact] - public async Task Should_add_errors_if_value_has_too_much_items() + public async Task Should_add_error_if_value_has_too_much_items() { var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId, MaxItems = 1 }); - await sut.ValidateAsync(CreateValue(Guid.NewGuid(), Guid.NewGuid()), errors); + await sut.ValidateAsync(CreateValue(ref1, ref2), errors, ValidationTestExtensions.References(ref1, ref2)); errors.Should().BeEquivalentTo( new[] { "Must have not more than 1 item(s)." }); } [Fact] - public async Task Should_add_errors_if_reference_are_not_valid() + public async Task Should_add_error_if_reference_are_not_valid() { - var referenceId = Guid.NewGuid(); - var sut = Field(new ReferencesFieldProperties { SchemaId = schemaId }); - await sut.ValidateAsync(CreateValue(referenceId), errors, ValidationTestExtensions.InvalidReferences(referenceId)); + await sut.ValidateAsync(CreateValue(ref1), errors, ValidationTestExtensions.References()); errors.Should().BeEquivalentTo( - new[] { $"Contains invalid reference '{referenceId}'." }); + new[] { $"Contains invalid reference '{ref1}'." }); } - private static JToken CreateValue(params Guid[] ids) + private static IJsonValue CreateValue(params Guid[] ids) { - return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType().ToArray()); + return ids == null ? (IJsonValue)JsonValue.Null : JsonValue.Array(ids.Select(x => (object)x.ToString()).ToArray()); } private static RootField Field(ReferencesFieldProperties properties) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs index 87dd5f4f6..6b83628f6 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs @@ -5,12 +5,13 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Collections; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -38,7 +39,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_string_is_required() + public async Task Should_add_error_if_string_is_required() { var sut = Field(new StringFieldProperties { IsRequired = true }); @@ -49,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_string_is_shorter_than_min_length() + public async Task Should_add_error_if_string_is_shorter_than_min_length() { var sut = Field(new StringFieldProperties { MinLength = 10 }); @@ -60,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_string_is_longer_than_max_length() + public async Task Should_add_error_if_string_is_longer_than_max_length() { var sut = Field(new StringFieldProperties { MaxLength = 5 }); @@ -71,9 +72,9 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_string_not_allowed() + public async Task Should_add_error_if_string_not_allowed() { - var sut = Field(new StringFieldProperties { AllowedValues = ImmutableList.Create("Foo") }); + var sut = Field(new StringFieldProperties { AllowedValues = ReadOnlyCollection.Create("Foo") }); await sut.ValidateAsync(CreateValue("Bar"), errors); @@ -82,7 +83,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_number_is_not_valid_pattern() + public async Task Should_add_error_if_number_is_not_valid_pattern() { var sut = Field(new StringFieldProperties { Pattern = "[0-9]{3}" }); @@ -93,7 +94,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_number_is_not_valid_pattern_with_message() + public async Task Should_add_error_if_number_is_not_valid_pattern_with_message() { var sut = Field(new StringFieldProperties { Pattern = "[0-9]{3}", PatternMessage = "Custom Error Message." }); @@ -103,9 +104,20 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent new[] { "Custom Error Message." }); } - private static JValue CreateValue(object v) + [Fact] + public async Task Should_add_error_if_unique_constraint_failed() + { + var sut = Field(new StringFieldProperties { IsUnique = true }); + + await sut.ValidateAsync(CreateValue("abc"), errors, ValidationTestExtensions.References(Guid.NewGuid())); + + errors.Should().BeEquivalentTo( + new[] { "Another content with the same value exists." }); + } + + private static IJsonValue CreateValue(string v) { - return new JValue(v); + return JsonValue.Create(v); } private static RootField Field(StringFieldProperties properties) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs index 6b763aadc..8cc8e046b 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs @@ -9,8 +9,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FluentAssertions; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure.Collections; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent @@ -48,7 +49,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_tags_are_required_and_null() + public async Task Should_add_error_if_tags_are_required_and_null() { var sut = Field(new TagsFieldProperties { IsRequired = true }); @@ -59,7 +60,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_tags_are_required_and_empty() + public async Task Should_add_error_if_tags_are_required_and_empty() { var sut = Field(new TagsFieldProperties { IsRequired = true }); @@ -70,18 +71,18 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_value_is_not_valid() + public async Task Should_add_error_if_value_is_not_valid() { var sut = Field(new TagsFieldProperties()); - await sut.ValidateAsync("invalid", errors); + await sut.ValidateAsync(JsonValue.Create("invalid"), errors); errors.Should().BeEquivalentTo( new[] { "Not a valid value." }); } [Fact] - public async Task Should_add_errors_if_value_has_not_enough_items() + public async Task Should_add_error_if_value_has_not_enough_items() { var sut = Field(new TagsFieldProperties { MinItems = 3 }); @@ -92,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_errors_if_value_has_too_much_items() + public async Task Should_add_error_if_value_has_too_much_items() { var sut = Field(new TagsFieldProperties { MaxItems = 1 }); @@ -102,9 +103,20 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent new[] { "Must have not more than 1 item(s)." }); } - private static JToken CreateValue(params string[] ids) + [Fact] + public async Task Should_add_error_if_value_contains_an_not_allowed_values() + { + var sut = Field(new TagsFieldProperties { AllowedValues = ReadOnlyCollection.Create("tag-2", "tag-3") }); + + await sut.ValidateAsync(CreateValue("tag-1", "tag-2", null), errors); + + errors.Should().BeEquivalentTo( + new[] { "[1]: Not an allowed value." }); + } + + private static IJsonValue CreateValue(params string[] ids) { - return ids == null ? JValue.CreateNull() : (JToken)new JArray(ids.OfType().ToArray()); + return ids == null ? (IJsonValue)JsonValue.Null : JsonValue.Array(ids.OfType().ToArray()); } private static RootField Field(TagsFieldProperties properties) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs index 3b1e1c454..38d111d7e 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs @@ -9,19 +9,19 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent.Validators; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { public static class ValidationTestExtensions { - private static readonly Task> ValidReferences = Task.FromResult>(new List()); - private static readonly Task> ValidAssets = Task.FromResult>(new List()); + private static readonly Task> EmptyReferences = Task.FromResult>(new List()); + private static readonly Task> EmptyAssets = Task.FromResult>(new List()); - public static readonly ValidationContext ValidContext = new ValidationContext((x, y) => ValidReferences, x => ValidAssets); + public static readonly ValidationContext ValidContext = new ValidationContext(Guid.NewGuid(), Guid.NewGuid(), (x, y) => EmptyReferences, x => EmptyAssets); public static Task ValidateAsync(this IValidator validator, object value, IList errors, ValidationContext context = null) { @@ -37,14 +37,14 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent CreateFormatter(errors)); } - public static Task ValidateAsync(this IField field, JToken value, IList errors, ValidationContext context = null) + public static Task ValidateAsync(this IField field, IJsonValue value, IList errors, ValidationContext context = null) { return new FieldValidator(ValidatorsFactory.CreateValidators(field).ToArray(), field).ValidateAsync(value, CreateContext(context), CreateFormatter(errors)); } - public static Task ValidateOptionalAsync(this IField field, JToken value, IList errors, ValidationContext context = null) + public static Task ValidateOptionalAsync(this IField field, IJsonValue value, IList errors, ValidationContext context = null) { return new FieldValidator(ValidatorsFactory.CreateValidators(field).ToArray(), field).ValidateAsync(value, CreateContext(context).Optional(true), @@ -75,14 +75,14 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent { var actual = Task.FromResult>(assets.ToList()); - return new ValidationContext((x, y) => ValidReferences, x => actual); + return new ValidationContext(Guid.NewGuid(), Guid.NewGuid(), (x, y) => EmptyReferences, x => actual); } - public static ValidationContext InvalidReferences(Guid referencesIds) + public static ValidationContext References(params Guid[] referencesIds) { - var actual = Task.FromResult>(new List { referencesIds }); + var actual = Task.FromResult>(referencesIds.ToList()); - return new ValidationContext((x, y) => actual, x => ValidAssets); + return new ValidationContext(Guid.NewGuid(), Guid.NewGuid(), (x, y) => actual, x => EmptyAssets); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValidatorTests.cs new file mode 100644 index 000000000..3c8e72881 --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/UniqueValidatorTests.cs @@ -0,0 +1,93 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Squidex.Domain.Apps.Core.ValidateContent; +using Squidex.Domain.Apps.Core.ValidateContent.Validators; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators +{ + public class UniqueValidatorTests + { + private readonly List errors = new List(); + private readonly Guid contentId = Guid.NewGuid(); + private readonly Guid schemaId = Guid.NewGuid(); + + [Fact] + public async Task Should_add_error_if_string_value_not_found() + { + var sut = new UniqueValidator(); + + var filter = string.Empty; + + await sut.ValidateAsync("hi", errors, Context(Guid.NewGuid(), f => filter = f)); + + errors.Should().BeEquivalentTo( + new[] { "property: Another content with the same value exists." }); + + Assert.Equal("Data.property.iv == 'hi'", filter); + } + + [Fact] + public async Task Should_add_error_if_double_value_not_found() + { + var sut = new UniqueValidator(); + + var filter = string.Empty; + + await sut.ValidateAsync(12.5, errors, Context(Guid.NewGuid(), f => filter = f)); + + errors.Should().BeEquivalentTo( + new[] { "property: Another content with the same value exists." }); + + Assert.Equal("Data.property.iv == 12.5", filter); + } + + [Fact] + public async Task Should_not_add_error_if_string_value_found() + { + var sut = new UniqueValidator(); + + var filter = string.Empty; + + await sut.ValidateAsync("hi", errors, Context(contentId, f => filter = f)); + + Assert.Empty(errors); + } + + [Fact] + public async Task Should_not_add_error_if_double_value_found() + { + var sut = new UniqueValidator(); + + var filter = string.Empty; + + await sut.ValidateAsync(12.5, errors, Context(contentId, f => filter = f)); + + Assert.Empty(errors); + } + + private ValidationContext Context(Guid id, Action filter) + { + return new ValidationContext(contentId, schemaId, + (schema, filterNode) => + { + filter(filterNode.ToString()); + + return Task.FromResult>(new List { id }); + }, + ids => + { + return Task.FromResult>(new List()); + }).Nested("property").Nested("iv"); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Core.Tests/TestData.cs b/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs similarity index 86% rename from tests/Squidex.Domain.Apps.Core.Tests/TestData.cs rename to tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs index 354dfa971..dec0ade55 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/TestData.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/TestUtils.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Collections.Immutable; using System.Linq; using System.Reflection; using Newtonsoft.Json; @@ -16,14 +15,18 @@ using Squidex.Domain.Apps.Core.Rules.Json; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas.Json; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Newtonsoft; using Xunit; namespace Squidex.Domain.Apps.Core { - public static class TestData + public static class TestUtils { - public static JsonSerializer DefaultSerializer() + public static readonly IJsonSerializer DefaultSerializer = CreateSerializer(); + + public static IJsonSerializer CreateSerializer(TypeNameHandling typeNameHandling = TypeNameHandling.Auto) { var typeNameRegistry = new TypeNameRegistry(); @@ -35,7 +38,10 @@ namespace Squidex.Domain.Apps.Core new AppClientsConverter(), new AppContributorsConverter(), new AppPatternsConverter(), + new ClaimsPrincipalConverter(), + new EnvelopeHeadersConverter(), new InstantConverter(), + new JsonValueConverter(), new LanguageConverter(), new LanguagesConfigConverter(), new NamedGuidIdConverter(), @@ -44,13 +50,13 @@ namespace Squidex.Domain.Apps.Core new RefTokenConverter(), new RolesConverter(), new RuleConverter(), - new SchemaConverter(new FieldRegistry(typeNameRegistry)), + new SchemaConverter(), new StringEnumConverter()), - TypeNameHandling = TypeNameHandling.Auto + TypeNameHandling = typeNameHandling }; - return JsonSerializer.Create(serializerSettings); + return new NewtonsoftJsonSerializer(serializerSettings); } public static Schema MixedSchema() @@ -85,7 +91,7 @@ namespace Squidex.Domain.Apps.Core .AddReferences(109, "root-references", Partitioning.Invariant, new ReferencesFieldProperties()) .AddString(110, "root-string1", Partitioning.Invariant, - new StringFieldProperties { Label = "My String1", IsRequired = true, AllowedValues = ImmutableList.Create("a", "b") }) + new StringFieldProperties { Label = "My String1", IsRequired = true, AllowedValues = ReadOnlyCollection.Create("a", "b") }) .AddString(111, "root-string2", Partitioning.Invariant, new StringFieldProperties { Hints = "My String1" }) .AddTags(112, "root-tags", Partitioning.Language, @@ -100,6 +106,11 @@ namespace Squidex.Domain.Apps.Core return schema; } + public static T SerializeAndDeserialize(this T value) + { + return DefaultSerializer.Deserialize(DefaultSerializer.Serialize(value)); + } + public static void TestFreeze(IFreezable freezable) { var sut = new AssetsFieldProperties(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsGrainTests.cs index 1e5a0b3e5..45ae6d1af 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/AppUISettingsGrainTests.cs @@ -8,7 +8,7 @@ using System; using System.Threading.Tasks; using FakeItEasy; -using Newtonsoft.Json.Linq; +using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.States; using Xunit; @@ -33,13 +33,12 @@ namespace Squidex.Domain.Apps.Entities.Apps [Fact] public async Task Should_set_setting() { - await sut.SetAsync(new JObject(new JProperty("key", 15)).AsJ()); + await sut.SetAsync(JsonValue.Object().Add("key", 15).AsJ()); var actual = await sut.GetAsync(); var expected = - new JObject( - new JProperty("key", 15)); + JsonValue.Object().Add("key", 15); Assert.Equal(expected.ToString(), actual.Value.ToString()); } @@ -47,13 +46,12 @@ namespace Squidex.Domain.Apps.Entities.Apps [Fact] public async Task Should_set_root_value() { - await sut.SetAsync("key", ((JToken)123).AsJ()); + await sut.SetAsync("key", JsonValue.Create(123).AsJ()); var actual = await sut.GetAsync(); var expected = - new JObject( - new JProperty("key", 123)); + JsonValue.Object().Add("key", 123); Assert.Equal(expected.ToString(), actual.Value.ToString()); } @@ -61,12 +59,12 @@ namespace Squidex.Domain.Apps.Entities.Apps [Fact] public async Task Should_remove_root_value() { - await sut.SetAsync("key", ((JToken)123).AsJ()); + await sut.SetAsync("key", JsonValue.Create(123).AsJ()); await sut.RemoveAsync("key"); var actual = await sut.GetAsync(); - var expected = new JObject(); + var expected = JsonValue.Object(); Assert.Equal(expected.ToString(), actual.Value.ToString()); } @@ -74,15 +72,13 @@ namespace Squidex.Domain.Apps.Entities.Apps [Fact] public async Task Should_set_nested_value() { - await sut.SetAsync("root.nested", ((JToken)123).AsJ()); + await sut.SetAsync("root.nested", JsonValue.Create(123).AsJ()); var actual = await sut.GetAsync(); var expected = - new JObject( - new JProperty("root", - new JObject( - new JProperty("nested", 123)))); + JsonValue.Object().Add("root", + JsonValue.Object().Add("nested", 123)); Assert.Equal(expected.ToString(), actual.Value.ToString()); } @@ -90,14 +86,14 @@ namespace Squidex.Domain.Apps.Entities.Apps [Fact] public async Task Should_remove_nested_value() { - await sut.SetAsync("root.nested", ((JToken)123).AsJ()); + await sut.SetAsync("root.nested", JsonValue.Create(123).AsJ()); await sut.RemoveAsync("root.nested"); var actual = await sut.GetAsync(); var expected = - new JObject( - new JProperty("root", new JObject())); + JsonValue.Object().Add("root", + JsonValue.Object()); Assert.Equal(expected.ToString(), actual.Value.ToString()); } @@ -105,9 +101,9 @@ namespace Squidex.Domain.Apps.Entities.Apps [Fact] public async Task Should_throw_exception_if_nested_not_an_object() { - await sut.SetAsync("root.nested", ((JToken)123).AsJ()); + await sut.SetAsync("root.nested", JsonValue.Create(123).AsJ()); - await Assert.ThrowsAsync(() => sut.SetAsync("root.nested.value", ((JToken)123).AsJ())); + await Assert.ThrowsAsync(() => sut.SetAsync("root.nested.value", JsonValue.Create(123).AsJ())); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RoleExtensionsRests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RoleExtensionsRests.cs index 3e530d6d9..5d4b8764d 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RoleExtensionsRests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/RoleExtensionsRests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Linq; using Squidex.Infrastructure.Security; using Xunit; @@ -16,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Apps [Fact] public void Should_add_common_permission() { - var source = new string[0]; + var source = Array.Empty(); var result = source.Prefix("my-app"); Assert.Equal(new[] { "squidex.apps.my-app.common" }, result); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs index 9e18c3108..01cf6bc52 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs @@ -11,8 +11,10 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using FakeItEasy; -using FluentAssertions; +using Squidex.Domain.Apps.Core; +using Squidex.Infrastructure; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.States; using Squidex.Infrastructure.Tasks; using Xunit; @@ -22,89 +24,149 @@ namespace Squidex.Domain.Apps.Entities.Backup public class BackupReaderWriterTests { private readonly IStreamNameResolver streamNameResolver = A.Fake(); + private readonly IJsonSerializer serializer = TestUtils.DefaultSerializer; + private readonly IEventDataFormatter formatter; + private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry(); + + [TypeName(nameof(MyEvent))] + public sealed class MyEvent : IEvent + { + public Guid GuidRaw { get; set; } + + public NamedId GuidNamed { get; set; } + + public Dictionary Values { get; set; } + } public BackupReaderWriterTests() { + typeNameRegistry.Map(typeof(MyEvent)); + + formatter = new DefaultEventDataFormatter(typeNameRegistry, serializer); + A.CallTo(() => streamNameResolver.WithNewId(A.Ignored, A>.Ignored)) .ReturnsLazily(new Func, string>((stream, idGenerator) => stream + "^2")); } - [Fact] - public async Task Should_write_and_read_events() + [Theory] + [InlineData(BackupVersion.V1)] + [InlineData(BackupVersion.V2)] + public async Task Should_write_and_read_events_to_backup(BackupVersion version) { var stream = new MemoryStream(); - var sourceEvents = new List(); + var random = new Random(); + var randomGuids = new List(); + + for (var i = 0; i < 100; i++) + { + randomGuids.Add(Guid.NewGuid()); + } + + Guid RandomGuid() + { + return randomGuids[random.Next(randomGuids.Count)]; + } + + var sourceEvents = new List<(string Stream, Envelope Event)>(); + + for (var i = 0; i < 200; i++) + { + var @event = new MyEvent + { + GuidNamed = new NamedId(RandomGuid(), $"name{i}"), + GuidRaw = RandomGuid(), + Values = new Dictionary + { + [RandomGuid()] = "Key" + } + }; + + var envelope = Envelope.Create(@event); - using (var writer = new BackupWriter(stream, true)) + envelope.Headers.Add(RandomGuid().ToString(), i); + envelope.Headers.Add("Id", RandomGuid().ToString()); + envelope.Headers.Add("Index", i); + + sourceEvents.Add(($"My-{RandomGuid()}", envelope)); + } + + using (var writer = new BackupWriter(serializer, stream, true, version)) { - for (var i = 0; i < 1000; i++) + foreach (var @event in sourceEvents) { - var eventData = new EventData { Type = i.ToString(), Metadata = i, Payload = i }; + var eventData = formatter.ToEventData(@event.Event, Guid.NewGuid(), true); var eventStored = new StoredEvent("S", "1", 2, eventData); - if (i % 17 == 0) - { - var localI = i; + var index = int.Parse(@event.Event.Headers["Index"].ToString()); - await writer.WriteBlobAsync(eventData.Type, innerStream => + if (index % 17 == 0) + { + await writer.WriteBlobAsync(index.ToString(), innerStream => { - innerStream.WriteByte((byte)localI); + innerStream.WriteByte((byte)index); return TaskHelper.Done; }); } - else if (i % 37 == 0) + else if (index % 37 == 0) { - await writer.WriteJsonAsync(eventData.Type, $"JSON_{i}"); + await writer.WriteJsonAsync(index.ToString(), $"JSON_{index}"); } writer.WriteEvent(eventStored); - - sourceEvents.Add(eventStored); } } stream.Position = 0; - var readEvents = new List(); + var targetEvents = new List<(string Stream, Envelope Event)>(); - using (var reader = new BackupReader(stream)) + using (var reader = new BackupReader(serializer, stream)) { - await reader.ReadEventsAsync(streamNameResolver, async @event => + await reader.ReadEventsAsync(streamNameResolver, formatter, async @event => { - var i = int.Parse(@event.Data.Type); + var index = int.Parse(@event.Event.Headers["Index"].ToString()); - if (i % 17 == 0) + if (index % 17 == 0) { - await reader.ReadBlobAsync(@event.Data.Type, innerStream => + await reader.ReadBlobAsync(index.ToString(), innerStream => { - var b = innerStream.ReadByte(); + var byteRead = innerStream.ReadByte(); - Assert.Equal((byte)i, b); + Assert.Equal((byte)index, byteRead); return TaskHelper.Done; }); } - else if (i % 37 == 0) + else if (index % 37 == 0) { - var j = await reader.ReadJsonAttachmentAsync(@event.Data.Type); + var json = await reader.ReadJsonAttachmentAsync(index.ToString()); - Assert.Equal($"JSON_{i}", j.ToString()); + Assert.Equal($"JSON_{index}", json); } - readEvents.Add(@event); + targetEvents.Add(@event); }); - } - var sourceEventsWithNewStreamName = - sourceEvents.Select(x => - new StoredEvent(streamNameResolver.WithNewId(x.StreamName, null), - x.EventPosition, - x.EventStreamNumber, - x.Data)).ToList(); + void CompareGuid(Guid source, Guid target) + { + Assert.Equal(source, reader.OldGuid(target)); + Assert.NotEqual(source, target); + } + + for (var i = 0; i < targetEvents.Count; i++) + { + var source = targetEvents[i].Event.To(); + + var target = sourceEvents[i].Event.To(); - readEvents.Should().BeEquivalentTo(sourceEventsWithNewStreamName); + CompareGuid(target.Payload.Values.First().Key, source.Payload.Values.First().Key); + CompareGuid(target.Payload.GuidRaw, source.Payload.GuidRaw); + CompareGuid(target.Payload.GuidNamed.Id, source.Payload.GuidNamed.Id); + CompareGuid(target.Headers.GetGuid("Id"), source.Headers.GetGuid("Id")); + } + } } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Backup/GuidMapperTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Backup/GuidMapperTests.cs deleted file mode 100644 index fdab2731a..000000000 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Backup/GuidMapperTests.cs +++ /dev/null @@ -1,161 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using Newtonsoft.Json.Linq; -using Xunit; - -namespace Squidex.Domain.Apps.Entities.Backup -{ - public class GuidMapperTests - { - private readonly Guid id1 = Guid.NewGuid(); - private readonly Guid id2 = Guid.NewGuid(); - private readonly GuidMapper map = new GuidMapper(); - - [Fact] - public void Should_map_guid_string_if_valid() - { - var result = map.NewGuidString(id1.ToString()); - - Assert.Equal(map.NewGuid(id1).ToString(), result); - } - - [Fact] - public void Should_return_null_if_mapping_invalid_guid_string() - { - var result = map.NewGuidString("invalid"); - - Assert.Null(result); - } - - [Fact] - public void Should_return_null_if_mapping_null_guid_string() - { - var result = map.NewGuidString(null); - - Assert.Null(result); - } - - [Fact] - public void Should_map_guid() - { - var result = map.NewGuids(id1); - - Assert.Equal(map.NewGuid(id1), result.Value()); - } - - [Fact] - public void Should_return_old_guid() - { - var newGuid = map.NewGuids(id1).Value(); - - Assert.Equal(id1, map.OldGuid(newGuid)); - } - - [Fact] - public void Should_map_guid_string() - { - var result = map.NewGuids(id1.ToString()); - - Assert.Equal(map.NewGuid(id1).ToString(), result.Value()); - } - - [Fact] - public void Should_map_named_id() - { - var result = map.NewGuids($"{id1},name"); - - Assert.Equal($"{map.NewGuid(id1)},name", result.Value()); - } - - [Fact] - public void Should_map_array_with_guid() - { - var obj = - new JObject( - new JProperty("k", - new JArray(id1, id1, id2))); - - map.NewGuids(obj); - - Assert.Equal(map.NewGuid(id1), obj["k"][0].Value()); - Assert.Equal(map.NewGuid(id1), obj["k"][1].Value()); - Assert.Equal(map.NewGuid(id2), obj["k"][2].Value()); - } - - [Fact] - public void Should_map_objects_with_guid_keys() - { - var obj = - new JObject( - new JProperty("k", - new JObject( - new JProperty(id1.ToString(), id1), - new JProperty(id2.ToString(), id2)))); - - map.NewGuids(obj); - - Assert.Equal(map.NewGuid(id1), obj["k"].Value(map.NewGuid(id1).ToString())); - Assert.Equal(map.NewGuid(id2), obj["k"].Value(map.NewGuid(id2).ToString())); - } - - [Fact] - public void Should_map_objects_with_guid() - { - var obj = - new JObject( - new JProperty("k", - new JObject( - new JProperty("v1", id1), - new JProperty("v2", id1), - new JProperty("v3", id2)))); - - map.NewGuids(obj); - - Assert.Equal(map.NewGuid(id1), obj["k"].Value("v1")); - Assert.Equal(map.NewGuid(id1), obj["k"].Value("v2")); - Assert.Equal(map.NewGuid(id2), obj["k"].Value("v3")); - } - - [Fact] - public void Should_map_objects_with_guid_string() - { - var obj = - new JObject( - new JProperty("k", - new JObject( - new JProperty("v1", id1.ToString()), - new JProperty("v2", id1.ToString()), - new JProperty("v3", id2.ToString())))); - - map.NewGuids(obj); - - Assert.Equal(map.NewGuid(id1).ToString(), obj["k"].Value("v1")); - Assert.Equal(map.NewGuid(id1).ToString(), obj["k"].Value("v2")); - Assert.Equal(map.NewGuid(id2).ToString(), obj["k"].Value("v3")); - } - - [Fact] - public void Should_map_objects_with_named_id() - { - var obj = - new JObject( - new JProperty("k", - new JObject( - new JProperty("v1", $"{id1},v1"), - new JProperty("v2", $"{id1},v2"), - new JProperty("v3", $"{id2},v3")))); - - map.NewGuids(obj); - - Assert.Equal($"{map.NewGuid(id1).ToString()},v1", obj["k"].Value("v1")); - Assert.Equal($"{map.NewGuid(id1).ToString()},v2", obj["k"].Value("v2")); - Assert.Equal($"{map.NewGuid(id2).ToString()},v3", obj["k"].Value("v3")); - } - } -} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs index ffa580607..56bddfe27 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentGrainTests.cs @@ -41,10 +41,10 @@ namespace Squidex.Domain.Apps.Entities.Contents new NamedContentData() .AddField("my-field1", new ContentFieldData() - .AddValue(null)) + .AddValue("iv", null)) .AddField("my-field2", new ContentFieldData() - .AddValue(1)); + .AddValue("iv", 1)); private readonly NamedContentData data = new NamedContentData() .AddField("my-field1", diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs index f2a90e843..ff0a07365 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLQueriesTests.cs @@ -8,7 +8,6 @@ using System; using System.Threading.Tasks; using FakeItEasy; -using Newtonsoft.Json.Linq; using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; using Xunit; @@ -77,9 +76,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { id = asset.Id, version = 1, - created = asset.Created.ToDateTimeUtc(), + created = asset.Created, createdBy = "subject:user1", - lastModified = asset.LastModified.ToDateTimeUtc(), + lastModified = asset.LastModified, lastModifiedBy = "subject:user2", url = $"assets/{asset.Id}", thumbnailUrl = $"assets/{asset.Id}?width=100", @@ -147,9 +146,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { id = asset.Id, version = 1, - created = asset.Created.ToDateTimeUtc(), + created = asset.Created, createdBy = "subject:user1", - lastModified = asset.LastModified.ToDateTimeUtc(), + lastModified = asset.LastModified, lastModifiedBy = "subject:user2", url = $"assets/{asset.Id}", thumbnailUrl = $"assets/{asset.Id}?width=100", @@ -211,9 +210,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { id = asset.Id, version = 1, - created = asset.Created.ToDateTimeUtc(), + created = asset.Created, createdBy = "subject:user1", - lastModified = asset.LastModified.ToDateTimeUtc(), + lastModified = asset.LastModified, lastModifiedBy = "subject:user2", url = $"assets/{asset.Id}", thumbnailUrl = $"assets/{asset.Id}?width=100", @@ -298,9 +297,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { id = content.Id, version = 1, - created = content.Created.ToDateTimeUtc(), + created = content.Created, createdBy = "subject:user1", - lastModified = content.LastModified.ToDateTimeUtc(), + lastModified = content.LastModified, lastModifiedBy = "subject:user2", status = "DRAFT", url = $"contents/my-schema/{content.Id}", @@ -320,7 +319,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL }, myDatetime = new { - iv = content.LastModified.ToDateTimeUtc() + iv = content.LastModified }, myJson = new { @@ -440,9 +439,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { id = content.Id, version = 1, - created = content.Created.ToDateTimeUtc(), + created = content.Created, createdBy = "subject:user1", - lastModified = content.LastModified.ToDateTimeUtc(), + lastModified = content.LastModified, lastModifiedBy = "subject:user2", status = "DRAFT", url = $"contents/my-schema/{content.Id}", @@ -462,7 +461,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL }, myDatetime = new { - iv = content.LastModified.ToDateTimeUtc() + iv = content.LastModified }, myJson = new { @@ -560,9 +559,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { id = content.Id, version = 1, - created = content.Created.ToDateTimeUtc(), + created = content.Created, createdBy = "subject:user1", - lastModified = content.LastModified.ToDateTimeUtc(), + lastModified = content.LastModified, lastModifiedBy = "subject:user2", status = "DRAFT", url = $"contents/my-schema/{content.Id}", @@ -582,7 +581,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL }, myDatetime = new { - iv = content.LastModified.ToDateTimeUtc() + iv = content.LastModified }, myJson = new { @@ -819,9 +818,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL var result = await sut.QueryAsync(context, new GraphQLQuery { Query = query }); - var json = JToken.FromObject(result); + var json = serializer.Serialize(result); - Assert.Null(json["data"]); + Assert.Contains("\"data\":null", json); } private QueryContext MatchsAssetContext() diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs index d46676a0e..6988c899d 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs @@ -12,7 +12,6 @@ using FakeItEasy; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using NodaTime.Extensions; using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core.Apps; @@ -23,6 +22,8 @@ using Squidex.Domain.Apps.Entities.Assets; using Squidex.Domain.Apps.Entities.Contents.TestData; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Objects; using Xunit; #pragma warning disable SA1311 // Static readonly fields must begin with upper-case letter @@ -39,6 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL protected readonly IContentQueryService contentQuery = A.Fake(); protected readonly IAssetQueryService assetQuery = A.Fake(); protected readonly ISchemaEntity schema = A.Fake(); + protected readonly IJsonSerializer serializer = TestUtils.CreateSerializer(TypeNameHandling.None); protected readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); protected readonly IAppProvider appProvider = A.Fake(); protected readonly IAppEntity app = A.Dummy(); @@ -106,40 +108,40 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL .AddValue("de", "value")) .AddField("my-assets", new ContentFieldData() - .AddValue("iv", JToken.FromObject(new[] { assetId }))) + .AddValue("iv", JsonValue.Array(assetId.ToString()))) .AddField("my-number", new ContentFieldData() - .AddValue("iv", 1)) + .AddValue("iv", 1.0)) .AddField("my-boolean", new ContentFieldData() .AddValue("iv", true)) .AddField("my-datetime", new ContentFieldData() - .AddValue("iv", now.ToDateTimeUtc())) + .AddValue("iv", now)) .AddField("my-tags", new ContentFieldData() - .AddValue("iv", JToken.FromObject(new[] { "tag1", "tag2" }))) + .AddValue("iv", JsonValue.Array("tag1", "tag2"))) .AddField("my-references", new ContentFieldData() - .AddValue("iv", JToken.FromObject(new[] { refId }))) + .AddValue("iv", JsonValue.Array(refId.ToString()))) .AddField("my-geolocation", new ContentFieldData() - .AddValue("iv", JToken.FromObject(new { latitude = 10, longitude = 20 }))) + .AddValue("iv", JsonValue.Object().Add("latitude", 10).Add("longitude", 20))) .AddField("my-json", new ContentFieldData() - .AddValue("iv", JToken.FromObject(new { value = 1 }))) + .AddValue("iv", JsonValue.Object().Add("value", 1))) .AddField("my-localized", new ContentFieldData() .AddValue("de-DE", "de-DE")) .AddField("my-array", new ContentFieldData() - .AddValue("iv", new JArray( - new JObject( - new JProperty("nested-boolean", true), - new JProperty("nested-number", 1)), - new JObject( - new JProperty("nested-boolean", false), - new JProperty("nested-number", 2))))); + .AddValue("iv", JsonValue.Array( + JsonValue.Object() + .Add("nested-boolean", true) + .Add("nested-number", 1), + JsonValue.Object() + .Add("nested-boolean", false) + .Add("nested-number", 2)))); var content = new ContentEntity { @@ -179,22 +181,22 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL return asset; } - protected static void AssertResult(object expected, (bool HasErrors, object Response) result, bool checkErrors = true) + protected void AssertResult(object expected, (bool HasErrors, object Response) result, bool checkErrors = true) { if (checkErrors && result.HasErrors) { throw new InvalidOperationException(Serialize(result)); } - var resultJson = JsonConvert.SerializeObject(result.Response, Formatting.Indented); - var expectJson = JsonConvert.SerializeObject(expected, Formatting.Indented); + var resultJson = serializer.Serialize(result.Response, true); + var expectJson = serializer.Serialize(expected, true); Assert.Equal(expectJson, resultJson); } - private static string Serialize((bool HasErrors, object Response) result) + private string Serialize((bool HasErrors, object Response) result) { - return JsonConvert.SerializeObject(result); + return serializer.Serialize(result); } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs index 9307fab0f..1b2b8c81e 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Collections.Immutable; using System.Linq; using FakeItEasy; using MongoDB.Bson.Serialization; @@ -20,6 +19,7 @@ using Squidex.Domain.Apps.Entities.MongoDb.Contents; using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.Queries; @@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb schemaDef = new Schema("user") .AddString(1, "firstName", Partitioning.Language, - new StringFieldProperties { Label = "FirstName", IsRequired = true, AllowedValues = ImmutableList.Create("1", "2") }) + new StringFieldProperties { Label = "FirstName", IsRequired = true, AllowedValues = ReadOnlyCollection.Create("1", "2") }) .AddString(2, "lastName", Partitioning.Language, new StringFieldProperties { Hints = "Last Name", Editor = StringFieldEditor.Input }) .AddBoolean(3, "isAdmin", Partitioning.Invariant, diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs index 8851bdc79..837387d2a 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/GuardRuleTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Collections.Immutable; using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Rules; @@ -15,6 +14,7 @@ using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; using Xunit; #pragma warning disable SA1310 // Field names must not contain underscore @@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { Trigger = new ContentChangedTrigger { - Schemas = ImmutableList.Empty + Schemas = ReadOnlyCollection.Empty() }, Action = null }); @@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { Trigger = new ContentChangedTrigger { - Schemas = ImmutableList.Empty + Schemas = ReadOnlyCollection.Empty() }, Action = new TestAction { @@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { Trigger = new ContentChangedTrigger { - Schemas = ImmutableList.Empty + Schemas = ReadOnlyCollection.Empty() }, Action = new TestAction { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs index cee1ddb81..4e1f6af25 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/Guards/Triggers/ContentChangedTriggerTests.cs @@ -6,11 +6,11 @@ // ========================================================================== using System; -using System.Collections.Immutable; using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Entities.Schemas; +using Squidex.Infrastructure.Collections; using Xunit; namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers @@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers var trigger = new ContentChangedTrigger { - Schemas = ImmutableList.Create( + Schemas = ReadOnlyCollection.Create( new ContentChangedTriggerSchema() ) }; @@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers { var trigger = new ContentChangedTrigger { - Schemas = ImmutableList.Empty + Schemas = ReadOnlyCollection.Empty() }; var errors = await RuleTriggerValidator.ValidateAsync(appId, trigger, appProvider); @@ -69,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards.Triggers var trigger = new ContentChangedTrigger { - Schemas = ImmutableList.Create( + Schemas = ReadOnlyCollection.Create( new ContentChangedTriggerSchema() ) }; diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDequeuerTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDequeuerTests.cs index 1705ca24d..68a3a2eb8 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDequeuerTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleDequeuerTests.cs @@ -8,7 +8,6 @@ using System; using System.Threading.Tasks; using FakeItEasy; -using Newtonsoft.Json.Linq; using NodaTime; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; @@ -49,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Rules [InlineData(4, 0, RuleResult.Failed, RuleJobResult.Failed)] public async Task Should_set_next_attempt_based_on_num_calls(int calls, int minutes, RuleResult result, RuleJobResult jobResult) { - var actionData = new JObject(); + var actionData = "{}"; var actionName = "MyAction"; var @event = CreateEvent(calls, actionName, actionData); @@ -73,7 +72,7 @@ namespace Squidex.Domain.Apps.Entities.Rules .MustHaveHappened(); } - private IRuleEventEntity CreateEvent(int numCalls, string actionName, JObject actionData) + private IRuleEventEntity CreateEvent(int numCalls, string actionName, string actionData) { var @event = A.Fake(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs index 80dd4b4d2..eae59aba9 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleGrainTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Collections.Immutable; using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Rules; @@ -16,6 +15,7 @@ using Squidex.Domain.Apps.Entities.Rules.State; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Domain.Apps.Events.Rules; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Log; using Xunit; @@ -191,7 +191,7 @@ namespace Squidex.Domain.Apps.Entities.Rules { var newTrigger = new ContentChangedTrigger { - Schemas = ImmutableList.Empty + Schemas = ReadOnlyCollection.Empty() }; var newAction = new TestAction @@ -206,7 +206,7 @@ namespace Squidex.Domain.Apps.Entities.Rules { var newTrigger = new ContentChangedTrigger { - Schemas = ImmutableList.Empty + Schemas = ReadOnlyCollection.Empty() }; var newAction = new TestAction diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs index f9d1c303c..a017a3f51 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs @@ -6,11 +6,11 @@ // ========================================================================== using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using FluentAssertions; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; using Xunit; namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties @@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties [Fact] public void Should_add_error_if_allowed_values_and_max_value_is_specified() { - var sut = new NumberFieldProperties { MaxValue = 10, AllowedValues = ImmutableList.Create(4d) }; + var sut = new NumberFieldProperties { MaxValue = 10, AllowedValues = ReadOnlyCollection.Create(4d) }; var errors = FieldPropertiesValidator.Validate(sut).ToList(); @@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties [Fact] public void Should_add_error_if_allowed_values_and_min_value_is_specified() { - var sut = new NumberFieldProperties { MinValue = 10, AllowedValues = ImmutableList.Create(4d) }; + var sut = new NumberFieldProperties { MinValue = 10, AllowedValues = ReadOnlyCollection.Create(4d) }; var errors = FieldPropertiesValidator.Validate(sut).ToList(); @@ -135,7 +135,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties [InlineData(NumberFieldEditor.Stars)] public void Should_add_error_if_inline_editing_is_not_allowed_for_editor(NumberFieldEditor editor) { - var sut = new NumberFieldProperties { InlineEditable = true, Editor = editor, AllowedValues = ImmutableList.Create(1.0) }; + var sut = new NumberFieldProperties { InlineEditable = true, Editor = editor, AllowedValues = ReadOnlyCollection.Create(1.0) }; var errors = FieldPropertiesValidator.Validate(sut).ToList(); @@ -151,7 +151,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties [InlineData(NumberFieldEditor.Dropdown)] public void Should_not_add_error_if_inline_editing_is_allowed_for_editor(NumberFieldEditor editor) { - var sut = new NumberFieldProperties { InlineEditable = true, Editor = editor, AllowedValues = ImmutableList.Create(1.0) }; + var sut = new NumberFieldProperties { InlineEditable = true, Editor = editor, AllowedValues = ReadOnlyCollection.Create(1.0) }; var errors = FieldPropertiesValidator.Validate(sut).ToList(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs index ed5382b4e..ad6faed6a 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs @@ -6,11 +6,11 @@ // ========================================================================== using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using FluentAssertions; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Collections; using Xunit; namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties @@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties [Fact] public void Should_add_error_if_allowed_values_and_max_value_is_specified() { - var sut = new StringFieldProperties { MinLength = 10, AllowedValues = ImmutableList.Create("4") }; + var sut = new StringFieldProperties { MinLength = 10, AllowedValues = ReadOnlyCollection.Create("4") }; var errors = FieldPropertiesValidator.Validate(sut).ToList(); @@ -48,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties [Fact] public void Should_add_error_if_allowed_values_and_min_value_is_specified() { - var sut = new StringFieldProperties { MaxLength = 10, AllowedValues = ImmutableList.Create("4") }; + var sut = new StringFieldProperties { MaxLength = 10, AllowedValues = ReadOnlyCollection.Create("4") }; var errors = FieldPropertiesValidator.Validate(sut).ToList(); @@ -108,7 +108,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties [InlineData(StringFieldEditor.TextArea)] public void Should_add_error_if_inline_editing_is_not_allowed_for_editor(StringFieldEditor editor) { - var sut = new StringFieldProperties { InlineEditable = true, Editor = editor, AllowedValues = ImmutableList.Create("Value") }; + var sut = new StringFieldProperties { InlineEditable = true, Editor = editor, AllowedValues = ReadOnlyCollection.Create("Value") }; var errors = FieldPropertiesValidator.Validate(sut).ToList(); @@ -125,7 +125,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties [InlineData(StringFieldEditor.Slug)] public void Should_not_add_error_if_inline_editing_is_allowed_for_editor(StringFieldEditor editor) { - var sut = new StringFieldProperties { InlineEditable = true, Editor = editor, AllowedValues = ImmutableList.Create("Value") }; + var sut = new StringFieldProperties { InlineEditable = true, Editor = editor, AllowedValues = ReadOnlyCollection.Create("Value") }; var errors = FieldPropertiesValidator.Validate(sut).ToList(); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs index 73719d63b..b456d3424 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs @@ -29,5 +29,33 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties new ValidationError("Max items must be greater than min items.", "MinItems", "MaxItems") }); } + + [Fact] + public void Should_add_error_if_radio_button_has_no_allowed_values() + { + var sut = new TagsFieldProperties { Editor = TagsFieldEditor.Checkboxes }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + errors.Should().BeEquivalentTo( + new List + { + new ValidationError("Checkboxes or dropdown list need allowed values.", "AllowedValues") + }); + } + + [Fact] + public void Should_add_error_if_editor_is_not_valid() + { + var sut = new TagsFieldProperties { Editor = (TagsFieldEditor)123 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + errors.Should().BeEquivalentTo( + new List + { + new ValidationError("Editor is not a valid value.", "Editor") + }); + } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj b/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj index a418b63ab..a854342e8 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj @@ -5,11 +5,6 @@ 2.1.1 Squidex.Domain.Apps.Entities - - - - - @@ -18,6 +13,7 @@ + diff --git a/tests/Squidex.Infrastructure.Tests/Caching/LRUCacheTests.cs b/tests/Squidex.Infrastructure.Tests/Caching/LRUCacheTests.cs index 4551fbe57..52e029193 100644 --- a/tests/Squidex.Infrastructure.Tests/Caching/LRUCacheTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Caching/LRUCacheTests.cs @@ -11,7 +11,7 @@ namespace Squidex.Infrastructure.Caching { public class LRUCacheTests { - private readonly LRUCache sut = new LRUCache(10); + private readonly LRUCache sut = new LRUCache(10); private readonly string key = "Key"; [Fact] diff --git a/tests/Squidex.Infrastructure.Tests/CollectionExtensionsTests.cs b/tests/Squidex.Infrastructure.Tests/CollectionExtensionsTests.cs index d540ff429..8a2851e74 100644 --- a/tests/Squidex.Infrastructure.Tests/CollectionExtensionsTests.cs +++ b/tests/Squidex.Infrastructure.Tests/CollectionExtensionsTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using System.Collections.Generic; -using System.Collections.Immutable; using Xunit; namespace Squidex.Infrastructure @@ -275,35 +274,5 @@ namespace Squidex.Infrastructure Assert.Equal(source, target); } - - [Fact] - public void Should_return_same_dictionary_if_item_to_replace_not_found() - { - var dict_0 = ImmutableDictionary.Empty; - var dict_1 = dict_0.SetItem(1, x => x); - - Assert.Same(dict_0, dict_1); - } - - [Fact] - public void Should_return_same_dictionary_if_replaced_item_is_same() - { - var dict_0 = ImmutableDictionary.Empty; - var dict_1 = dict_0.SetItem(1, 1); - var dict_2 = dict_1.SetItem(1, x => x); - - Assert.Same(dict_1, dict_2); - } - - [Fact] - public void Should_return_new_dictionary_if_updated_item_is_different() - { - var dict_0 = ImmutableDictionary.Empty; - var dict_1 = dict_0.SetItem(2, 2); - var dict_2 = dict_1.SetItem(2, x => 2 * x); - - Assert.NotSame(dict_1, dict_2); - Assert.Equal(4, dict_2[2]); - } } } \ No newline at end of file diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventDataFormatterTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventDataFormatterTests.cs index aef438ac2..60e2b769f 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventDataFormatterTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/DefaultEventDataFormatterTests.cs @@ -7,9 +7,7 @@ using System; using System.Linq; -using Newtonsoft.Json; using NodaTime; -using Squidex.Infrastructure.Json; using Squidex.Infrastructure.TestHelpers; using Xunit; @@ -27,18 +25,16 @@ namespace Squidex.Infrastructure.EventSourcing } } - private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); - private readonly TypeNameRegistry typeNameRegistry = new TypeNameRegistry(); private readonly DefaultEventDataFormatter sut; public DefaultEventDataFormatterTests() { - serializerSettings.Converters.Add(new PropertiesBagConverter()); + var typeNameRegistry = + new TypeNameRegistry() + .Map(typeof(MyEvent), "Event") + .Map(typeof(MyOldEvent), "OldEvent"); - typeNameRegistry.Map(typeof(MyEvent), "Event"); - typeNameRegistry.Map(typeof(MyOldEvent), "OldEvent"); - - sut = new DefaultEventDataFormatter(typeNameRegistry, JsonSerializer.Create(serializerSettings)); + sut = new DefaultEventDataFormatter(typeNameRegistry, JsonHelper.CreateSerializer(typeNameRegistry)); } [Fact] @@ -92,9 +88,9 @@ namespace Squidex.Infrastructure.EventSourcing Assert.Equal(inputEvent.Payload.MyProperty, outputEvent.Payload.MyProperty); } - private static void AssertHeaders(PropertiesBag lhs, PropertiesBag rhs) + private static void AssertHeaders(EnvelopeHeaders lhs, EnvelopeHeaders rhs) { - foreach (var key in lhs.PropertyNames.Concat(rhs.PropertyNames).Distinct()) + foreach (var key in lhs.Keys.Concat(rhs.Keys).Distinct()) { Assert.Equal(lhs[key].ToString(), rhs[key].ToString()); } diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs index 7ad64cdb2..464585379 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeExtensionsTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Globalization; using NodaTime; using Xunit; @@ -15,17 +14,16 @@ namespace Squidex.Infrastructure.EventSourcing public class EnvelopeExtensionsTests { private readonly Envelope sut = new Envelope(string.Empty); - private readonly CultureInfo culture = CultureInfo.InvariantCulture; [Fact] public void Should_set_and_get_timestamp() { - var timestamp = SystemClock.Instance.GetCurrentInstant(); + var timestamp = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds()); sut.SetTimestamp(timestamp); Assert.Equal(timestamp, sut.Headers.Timestamp()); - Assert.Equal(timestamp, sut.Headers["Timestamp"].ToInstant(culture)); + Assert.Equal(timestamp, sut.Headers.GetInstant("Timestamp")); } [Fact] @@ -36,7 +34,7 @@ namespace Squidex.Infrastructure.EventSourcing sut.SetCommitId(commitId); Assert.Equal(commitId, sut.Headers.CommitId()); - Assert.Equal(commitId, sut.Headers["CommitId"].ToGuid(culture)); + Assert.Equal(commitId, sut.Headers.GetGuid("CommitId")); } [Fact] @@ -47,7 +45,7 @@ namespace Squidex.Infrastructure.EventSourcing sut.SetEventId(commitId); Assert.Equal(commitId, sut.Headers.EventId()); - Assert.Equal(commitId, sut.Headers["EventId"].ToGuid(culture)); + Assert.Equal(commitId, sut.Headers.GetGuid("EventId")); } [Fact] @@ -58,7 +56,7 @@ namespace Squidex.Infrastructure.EventSourcing sut.SetAggregateId(commitId); Assert.Equal(commitId, sut.Headers.AggregateId()); - Assert.Equal(commitId, sut.Headers["AggregateId"].ToGuid(culture)); + Assert.Equal(commitId, sut.Headers.GetGuid("AggregateId")); } [Fact] @@ -80,7 +78,7 @@ namespace Squidex.Infrastructure.EventSourcing sut.SetEventStreamNumber(eventStreamNumber); Assert.Equal(eventStreamNumber, sut.Headers.EventStreamNumber()); - Assert.Equal(eventStreamNumber, sut.Headers["EventStreamNumber"].ToInt64(culture)); + Assert.Equal(eventStreamNumber, sut.Headers.GetLong("EventStreamNumber")); } } } diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeaderTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeadersTests.cs similarity index 61% rename from tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeaderTests.cs rename to tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeadersTests.cs index c3f4b091c..4a2a0286e 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeaderTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeHeadersTests.cs @@ -6,32 +6,27 @@ // ========================================================================== using System.Linq; +using Squidex.Infrastructure.Json.Objects; +using Squidex.Infrastructure.TestHelpers; using Xunit; namespace Squidex.Infrastructure.EventSourcing { - public class EnvelopeHeaderTests + public class EnvelopeHeadersTests { [Fact] public void Should_create_headers() { var headers = new EnvelopeHeaders(); - Assert.Equal(0, headers.Count); - } - - [Fact] - public void Should_create_headers_with_null_properties() - { - var headers = new EnvelopeHeaders(null); - - Assert.Equal(0, headers.Count); + Assert.Empty(headers); } [Fact] public void Should_create_headers_as_copy() { - var source = new PropertiesBag().Set("Key1", 123); + var source = JsonValue.Object().Add("Key1", 123); + var headers = new EnvelopeHeaders(source); CompareHeaders(headers, source); @@ -40,17 +35,26 @@ namespace Squidex.Infrastructure.EventSourcing [Fact] public void Should_clone_headers() { - var source = new PropertiesBag().Set("Key1", 123); - var headers = new EnvelopeHeaders(source); + var headers = new EnvelopeHeaders(JsonValue.Object().Add("Key1", 123)); var clone = headers.Clone(); CompareHeaders(headers, clone); } - private static void CompareHeaders(PropertiesBag lhs, PropertiesBag rhs) + [Fact] + public void Should_serialize_and_deserialize() + { + var value = new EnvelopeHeaders(JsonValue.Object().Add("Key1", 123)); + + var deserialized = value.SerializeAndDeserialize(); + + CompareHeaders(deserialized, value); + } + + private static void CompareHeaders(JsonObject lhs, JsonObject rhs) { - foreach (var key in lhs.PropertyNames.Concat(rhs.PropertyNames).Distinct()) + foreach (var key in lhs.Keys.Concat(rhs.Keys).Distinct()) { Assert.Equal(lhs[key].ToString(), rhs[key].ToString()); } diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeTests.cs index 804d57f14..f9d3a75e5 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/EnvelopeTests.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure.Json; using Squidex.Infrastructure.TestHelpers; using Xunit; @@ -23,7 +22,7 @@ namespace Squidex.Infrastructure.EventSourcing { var value = new Envelope(new MyEvent { Value = 1 }); - var deserialized = value.SerializeAndDeserializeAndReturn(new PropertiesBagConverter()); + var deserialized = value.SerializeAndDeserialize(); Assert.Equal(1, deserialized.To().Payload.Value); } diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs index da62e3e23..6c3ae202b 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/Grains/EventConsumerGrainTests.cs @@ -49,7 +49,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains private readonly ISemanticLog log = A.Fake(); private readonly IStore store = A.Fake>(); private readonly IEventDataFormatter formatter = A.Fake(); - private readonly EventData eventData = new EventData(); + private readonly EventData eventData = new EventData("Type", new EnvelopeHeaders(), "Payload"); private readonly Envelope envelope = new Envelope(new MyEvent()); private readonly EventConsumerGrain sut; private readonly string consumerName; @@ -64,7 +64,10 @@ namespace Squidex.Infrastructure.EventSourcing.Grains consumerName = eventConsumer.GetType().Name; A.CallTo(() => store.WithSnapshots(A.Ignored, consumerName, A>.Ignored)) - .Invokes(new Action>((t, key, a) => apply = a)) + .Invokes(new Action>((t, key, a) => + { + apply = a; + })) .Returns(persistence); A.CallTo(() => eventStore.CreateSubscription(A.Ignored, A.Ignored, A.Ignored)) @@ -79,7 +82,8 @@ namespace Squidex.Infrastructure.EventSourcing.Grains A.CallTo(() => persistence.WriteSnapshotAsync(A.Ignored)) .Invokes(new Action(s => state = s)); - A.CallTo(() => formatter.Parse(eventData, true)).Returns(envelope); + A.CallTo(() => formatter.Parse(eventData, null)) + .Returns(envelope); sut = new MyEventConsumerGrain( x => eventConsumer, @@ -192,7 +196,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains [Fact] public async Task Should_ignore_old_events() { - A.CallTo(() => formatter.Parse(eventData, true)) + A.CallTo(() => formatter.Parse(eventData, null)) .Throws(new TypeNameNotFoundException()); var @event = new StoredEvent("Stream", Guid.NewGuid().ToString(), 123, eventData); @@ -326,7 +330,7 @@ namespace Squidex.Infrastructure.EventSourcing.Grains { var ex = new InvalidOperationException(); - A.CallTo(() => formatter.Parse(eventData, true)) + A.CallTo(() => formatter.Parse(eventData, null)) .Throws(ex); var @event = new StoredEvent("Stream", Guid.NewGuid().ToString(), 123, eventData); diff --git a/tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs b/tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs index 85aee50d2..10ad267e9 100644 --- a/tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs +++ b/tests/Squidex.Infrastructure.Tests/EventSourcing/RetrySubscriptionTests.cs @@ -90,7 +90,7 @@ namespace Squidex.Infrastructure.EventSourcing [Fact] public async Task Should_forward_event_from_inner_subscription() { - var ev = new StoredEvent("Stream", "1", 2, new EventData()); + var ev = new StoredEvent("Stream", "1", 2, new EventData("Type", new EnvelopeHeaders(), "Payload")); await OnEventAsync(eventSubscription, ev); await sut.StopAsync(); @@ -102,7 +102,7 @@ namespace Squidex.Infrastructure.EventSourcing [Fact] public async Task Should_not_forward_event_when_message_is_from_another_subscription() { - var ev = new StoredEvent("Stream", "1", 2, new EventData()); + var ev = new StoredEvent("Stream", "1", 2, new EventData("Type", new EnvelopeHeaders(), "Payload")); await OnEventAsync(A.Fake(), ev); await sut.StopAsync(); diff --git a/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs b/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs index 6abf98d8a..76e65659e 100644 --- a/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Json/ClaimsPrincipalConverterTests.cs @@ -36,10 +36,10 @@ namespace Squidex.Infrastructure.Json "Google") }); - var result = value.SerializeAndDeserializeAndReturn(new ClaimsPrincipalConverter()); + var serialized = value.SerializeAndDeserialize(); - Assert.Equal(value.Identities.ElementAt(0).AuthenticationType, result.Identities.ElementAt(0).AuthenticationType); - Assert.Equal(value.Identities.ElementAt(1).AuthenticationType, result.Identities.ElementAt(1).AuthenticationType); + Assert.Equal(value.Identities.ElementAt(0).AuthenticationType, serialized.Identities.ElementAt(0).AuthenticationType); + Assert.Equal(value.Identities.ElementAt(1).AuthenticationType, serialized.Identities.ElementAt(1).AuthenticationType); } [Fact] @@ -47,7 +47,9 @@ namespace Squidex.Infrastructure.Json { ClaimsPrincipal value = null; - value.SerializeAndDeserialize(new ClaimsPrincipalConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Null(value); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Json/InstantConverterTests.cs b/tests/Squidex.Infrastructure.Tests/Json/InstantConverterTests.cs index b207d365a..aba2d8269 100644 --- a/tests/Squidex.Infrastructure.Tests/Json/InstantConverterTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Json/InstantConverterTests.cs @@ -19,7 +19,9 @@ namespace Squidex.Infrastructure.Json { var value = Instant.FromDateTimeUtc(DateTime.UtcNow.Date); - value.SerializeAndDeserialize(new InstantConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } [Fact] @@ -27,7 +29,9 @@ namespace Squidex.Infrastructure.Json { Instant? value = Instant.FromDateTimeUtc(DateTime.UtcNow.Date); - value.SerializeAndDeserialize(new InstantConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } [Fact] @@ -35,7 +39,9 @@ namespace Squidex.Infrastructure.Json { Instant? value = null; - value.SerializeAndDeserialize(new InstantConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Json/ConverterContractResolverTests.cs b/tests/Squidex.Infrastructure.Tests/Json/Newtonsoft/ConverterContractResolverTests.cs similarity index 94% rename from tests/Squidex.Infrastructure.Tests/Json/ConverterContractResolverTests.cs rename to tests/Squidex.Infrastructure.Tests/Json/Newtonsoft/ConverterContractResolverTests.cs index d96f8a780..4ea3d7c0f 100644 --- a/tests/Squidex.Infrastructure.Tests/Json/ConverterContractResolverTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Json/Newtonsoft/ConverterContractResolverTests.cs @@ -11,7 +11,7 @@ using NodaTime; using Squidex.Infrastructure.TestHelpers; using Xunit; -namespace Squidex.Infrastructure.Json +namespace Squidex.Infrastructure.Json.Newtonsoft { public class ConverterContractResolverTests { @@ -77,7 +77,9 @@ namespace Squidex.Infrastructure.Json { var value = Instant.FromDateTimeUtc(DateTime.UtcNow.Date); - value.SerializeAndDeserialize(new ConverterContractResolver(new InstantConverter())); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs b/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs new file mode 100644 index 000000000..5ad034901 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonObjectTests.cs @@ -0,0 +1,357 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NodaTime; +using Xunit; + +namespace Squidex.Infrastructure.Json.Objects +{ + public class JsonObjectTests + { + [Fact] + public void Should_make_correct_object_equal_comparisons() + { + var obj_count1_key1_val1_a = JsonValue.Object().Add("key1", 1); + var obj_count1_key1_val1_b = JsonValue.Object().Add("key1", 1); + + var obj_count1_key1_val2 = JsonValue.Object().Add("key1", 2); + var obj_count1_key2_val1 = JsonValue.Object().Add("key2", 1); + var obj_count2_key1_val1 = JsonValue.Object().Add("key1", 1).Add("key2", 2); + + var number = JsonValue.Create(1); + + Assert.Equal(obj_count1_key1_val1_a, obj_count1_key1_val1_b); + Assert.Equal(obj_count1_key1_val1_a.GetHashCode(), obj_count1_key1_val1_b.GetHashCode()); + Assert.True(obj_count1_key1_val1_a.Equals((object)obj_count1_key1_val1_b)); + + Assert.NotEqual(obj_count1_key1_val1_a, obj_count1_key1_val2); + Assert.NotEqual(obj_count1_key1_val1_a.GetHashCode(), obj_count1_key1_val2.GetHashCode()); + Assert.False(obj_count1_key1_val1_a.Equals((object)obj_count1_key1_val2)); + + Assert.NotEqual(obj_count1_key1_val1_a, obj_count1_key2_val1); + Assert.NotEqual(obj_count1_key1_val1_a.GetHashCode(), obj_count1_key2_val1.GetHashCode()); + Assert.False(obj_count1_key1_val1_a.Equals((object)obj_count1_key2_val1)); + + Assert.NotEqual(obj_count1_key1_val1_a, obj_count2_key1_val1); + Assert.NotEqual(obj_count1_key1_val1_a.GetHashCode(), obj_count2_key1_val1.GetHashCode()); + Assert.False(obj_count1_key1_val1_a.Equals((object)obj_count2_key1_val1)); + + Assert.NotEqual(obj_count1_key1_val1_a, number); + Assert.NotEqual(obj_count1_key1_val1_a.GetHashCode(), number.GetHashCode()); + Assert.False(obj_count1_key1_val1_a.Equals((object)number)); + } + + [Fact] + public void Should_make_correct_array_equal_comparisons() + { + var array_count1_val1_a = JsonValue.Array(1); + var array_count1_val1_b = JsonValue.Array(1); + + var array_count1_val2 = JsonValue.Array(2); + var array_count2_val1 = JsonValue.Array(1, 2); + + var number = JsonValue.Create(1); + + Assert.Equal(array_count1_val1_a, array_count1_val1_b); + Assert.Equal(array_count1_val1_a.GetHashCode(), array_count1_val1_b.GetHashCode()); + Assert.True(array_count1_val1_a.Equals((object)array_count1_val1_b)); + + Assert.NotEqual(array_count1_val1_a, array_count1_val2); + Assert.NotEqual(array_count1_val1_a.GetHashCode(), array_count1_val2.GetHashCode()); + Assert.False(array_count1_val1_a.Equals((object)array_count1_val2)); + + Assert.NotEqual(array_count1_val1_a, array_count2_val1); + Assert.NotEqual(array_count1_val1_a.GetHashCode(), array_count2_val1.GetHashCode()); + Assert.False(array_count1_val1_a.Equals((object)array_count2_val1)); + + Assert.NotEqual(array_count1_val1_a, number); + Assert.NotEqual(array_count1_val1_a.GetHashCode(), number.GetHashCode()); + Assert.False(array_count1_val1_a.Equals((object)number)); + } + + [Fact] + public void Should_make_correct_array_scalar_comparisons() + { + var number_val1_a = JsonValue.Create(1); + var number_val1_b = JsonValue.Create(1); + + var number_val2 = JsonValue.Create(2); + + var boolean = JsonValue.True; + + Assert.Equal(number_val1_a, number_val1_b); + Assert.Equal(number_val1_a.GetHashCode(), number_val1_b.GetHashCode()); + Assert.True(number_val1_a.Equals((object)number_val1_b)); + + Assert.NotEqual(number_val1_a, number_val2); + Assert.NotEqual(number_val1_a.GetHashCode(), number_val2.GetHashCode()); + Assert.False(number_val1_a.Equals((object)number_val2)); + + Assert.NotEqual(number_val1_a, boolean); + Assert.NotEqual(number_val1_a.GetHashCode(), boolean.GetHashCode()); + Assert.False(number_val1_a.Equals((object)boolean)); + } + + [Fact] + public void Should_make_correct_null_comparisons() + { + var null_a = JsonValue.Null; + var null_b = JsonValue.Null; + + var boolean = JsonValue.True; + + Assert.Equal(null_a, null_b); + Assert.Equal(null_a.GetHashCode(), null_b.GetHashCode()); + Assert.True(null_a.Equals((object)null_b)); + + Assert.NotEqual(null_a, boolean); + Assert.NotEqual(null_a.GetHashCode(), boolean.GetHashCode()); + Assert.False(null_a.Equals((object)boolean)); + } + + [Fact] + public void Should_cache_null() + { + Assert.Same(JsonValue.Null, JsonValue.Create((string)null)); + Assert.Same(JsonValue.Null, JsonValue.Create((bool?)null)); + Assert.Same(JsonValue.Null, JsonValue.Create((double?)null)); + Assert.Same(JsonValue.Null, JsonValue.Create((object)null)); + Assert.Same(JsonValue.Null, JsonValue.Create((Instant?)null)); + } + + [Fact] + public void Should_cache_true() + { + Assert.Same(JsonValue.True, JsonValue.Create(true)); + } + + [Fact] + public void Should_cache_false() + { + Assert.Same(JsonValue.False, JsonValue.Create(false)); + } + + [Fact] + public void Should_cache_empty() + { + Assert.Same(JsonValue.Empty, JsonValue.Create(string.Empty)); + } + + [Fact] + public void Should_cache_zero() + { + Assert.Same(JsonValue.Zero, JsonValue.Create(0)); + } + + [Fact] + public void Should_boolean_from_object() + { + Assert.Equal(JsonValue.True, JsonValue.Create((object)true)); + } + + [Fact] + public void Should_create_value_from_instant() + { + var instant = Instant.FromUnixTimeSeconds(4123125455); + + Assert.Equal(instant.ToString(), JsonValue.Create(instant).ToString()); + } + + [Fact] + public void Should_create_value_from_instant_object() + { + var instant = Instant.FromUnixTimeSeconds(4123125455); + + Assert.Equal(instant.ToString(), JsonValue.Create((object)instant).ToString()); + } + + [Fact] + public void Should_create_array() + { + var json = JsonValue.Array(1, "2"); + + Assert.Equal("[1, \"2\"]", json.ToJsonString()); + Assert.Equal("[1, \"2\"]", json.ToString()); + } + + [Fact] + public void Should_create_object() + { + var json = JsonValue.Object().Add("key1", 1).Add("key2", "2"); + + Assert.Equal("{\"key1\":1, \"key2\":\"2\"}", json.ToJsonString()); + Assert.Equal("{\"key1\":1, \"key2\":\"2\"}", json.ToString()); + } + + [Fact] + public void Should_create_number() + { + var json = JsonValue.Create(123); + + Assert.Equal("123", json.ToJsonString()); + Assert.Equal("123", json.ToString()); + } + + [Fact] + public void Should_create_boolean_true() + { + var json = JsonValue.Create(true); + + Assert.Equal("true", json.ToJsonString()); + Assert.Equal("true", json.ToString()); + } + + [Fact] + public void Should_create_boolean_false() + { + var json = JsonValue.Create(false); + + Assert.Equal("false", json.ToJsonString()); + Assert.Equal("false", json.ToString()); + } + + [Fact] + public void Should_create_string() + { + var json = JsonValue.Create("hi"); + + Assert.Equal("\"hi\"", json.ToJsonString()); + Assert.Equal("hi", json.ToString()); + } + + [Fact] + public void Should_create_null() + { + var json = JsonValue.Create((object)null); + + Assert.Equal("null", json.ToJsonString()); + Assert.Equal("null", json.ToString()); + } + + [Fact] + public void Should_create_arrays_in_different_ways() + { + var numbers = new[] + { + JsonValue.Array(1.0f, 2.0f), + JsonValue.Array(JsonValue.Create(1.0f), JsonValue.Create(2.0f)) + }; + + Assert.Single(numbers.Distinct()); + Assert.Single(numbers.Select(x => x.GetHashCode()).Distinct()); + } + + [Fact] + public void Should_create_number_from_types() + { + var numbers = new[] + { + JsonValue.Create(12.0f), + JsonValue.Create(12.0), + JsonValue.Create(12L), + JsonValue.Create(12), + JsonValue.Create((object)12.0d), + JsonValue.Create((double?)12.0d) + }; + + Assert.Single(numbers.Distinct()); + Assert.Single(numbers.Select(x => x.GetHashCode()).Distinct()); + } + + [Fact] + public void Should_create_null_when_adding_null_to_array() + { + var array = JsonValue.Array(); + + array.Add(null); + + Assert.Same(JsonValue.Null, array[0]); + } + + [Fact] + public void Should_create_null_when_replacing_to_null_in_array() + { + var array = JsonValue.Array(1); + + array[0] = null; + + Assert.Same(JsonValue.Null, array[0]); + } + + [Fact] + public void Should_create_null_when_adding_null_to_object() + { + var obj = JsonValue.Object(); + + obj.Add("key", null); + + Assert.Same(JsonValue.Null, obj["key"]); + } + + [Fact] + public void Should_create_null_when_replacing_to_null_object() + { + var obj = JsonValue.Object(); + + obj["key"] = null; + + Assert.Same(JsonValue.Null, obj["key"]); + } + + [Fact] + public void Should_remove_value_from_object() + { + var obj = JsonValue.Object().Add("key", 1); + + obj.Remove("key"); + + Assert.False(obj.TryGetValue("key", out _)); + Assert.False(obj.ContainsKey("key")); + } + + [Fact] + public void Should_clear_values_from_object() + { + var obj = JsonValue.Object().Add("key", 1); + + obj.Clear(); + + Assert.False(obj.TryGetValue("key", out _)); + Assert.False(obj.ContainsKey("key")); + } + + [Fact] + public void Should_provide_collection_values_from_object() + { + var obj = JsonValue.Object().Add("11", "44").Add("22", "88"); + + var kvps = new[] + { + new KeyValuePair("11", JsonValue.Create("44")), + new KeyValuePair("22", JsonValue.Create("88")) + }; + + Assert.Equal(2, obj.Count); + + Assert.Equal(new[] { "11", "22" }, obj.Keys); + Assert.Equal(new[] { "44", "88" }, obj.Values.Select(x => x.ToString())); + + Assert.Equal(kvps, obj.ToArray()); + Assert.Equal(kvps, ((IEnumerable)obj).OfType>().ToArray()); + } + + [Fact] + public void Should_throw_exception_when_creation_value_from_invalid_type() + { + Assert.Throws(() => JsonValue.Create(Guid.Empty)); + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonValuesSerializationTests.cs b/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonValuesSerializationTests.cs new file mode 100644 index 000000000..08150c3a8 --- /dev/null +++ b/tests/Squidex.Infrastructure.Tests/Json/Objects/JsonValuesSerializationTests.cs @@ -0,0 +1,113 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure.TestHelpers; +using Xunit; + +namespace Squidex.Infrastructure.Json.Objects +{ + public class JsonValuesSerializationTests + { + [Fact] + public void Should_deserialize_integer() + { + var serialized = JsonHelper.Deserialize(123); + + Assert.Equal(JsonValue.Create(123), serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_null() + { + var value = JsonValue.Null; + + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_date() + { + var value = JsonValue.Create("2008-09-15T15:53:00"); + + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_string() + { + var value = JsonValue.Create("my-string"); + + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_boolean() + { + var value = JsonValue.Create(true); + + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_number() + { + var value = JsonValue.Create(123); + + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_array() + { + var value = JsonValue.Array(1, 2); + + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_object() + { + var value = + JsonValue.Object() + .Add("1", 1) + .Add("2", 1); + + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); + } + + [Fact] + public void Should_serialize_and_deserialize_complex_object() + { + var value = + JsonValue.Object() + .Add("1", + JsonValue.Array( + JsonValue.Object().Add("1_1", 11), + JsonValue.Object().Add("1_2", 12))) + .Add("2", + JsonValue.Object().Add("2_1", 11)); + + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); + } + } +} diff --git a/tests/Squidex.Infrastructure.Tests/LanguageTests.cs b/tests/Squidex.Infrastructure.Tests/LanguageTests.cs index cddf49510..f57e30fe7 100644 --- a/tests/Squidex.Infrastructure.Tests/LanguageTests.cs +++ b/tests/Squidex.Infrastructure.Tests/LanguageTests.cs @@ -7,7 +7,6 @@ using System; using System.Linq; -using Squidex.Infrastructure.Json; using Squidex.Infrastructure.TestHelpers; using Xunit; @@ -125,7 +124,9 @@ namespace Squidex.Infrastructure { Language value = null; - value.SerializeAndDeserialize(new LanguageConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } [Fact] @@ -133,7 +134,9 @@ namespace Squidex.Infrastructure { var value = Language.DE; - value.SerializeAndDeserialize(new LanguageConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Log/JsonLogWriterTests.cs b/tests/Squidex.Infrastructure.Tests/Log/JsonLogWriterTests.cs index a0ff04d7a..069be90c7 100644 --- a/tests/Squidex.Infrastructure.Tests/Log/JsonLogWriterTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Log/JsonLogWriterTests.cs @@ -13,7 +13,7 @@ namespace Squidex.Infrastructure.Log { public class JsonLogWriterTests { - private readonly IObjectWriter sut = new JsonLogWriter(); + private readonly IObjectWriter sut = JsonLogWriterFactory.Default().Create(); [Fact] public void Should_write_boolean_property() @@ -59,6 +59,7 @@ namespace Squidex.Infrastructure.Log public void Should_write_datetimeoffset_property() { var value = DateTimeOffset.UtcNow; + var result = sut.WriteProperty("property", value).ToString(); Assert.Equal($"{{\"property\":\"{value:o}\"}}", result); @@ -68,6 +69,7 @@ namespace Squidex.Infrastructure.Log public void Should_write_date_property() { var value = DateTime.UtcNow; + var result = sut.WriteProperty("property", value).ToString(); Assert.Equal($"{{\"property\":\"{value:o}\"}}", result); @@ -125,6 +127,7 @@ namespace Squidex.Infrastructure.Log public void Should_write_datetimeoffset_value() { var value = DateTimeOffset.UtcNow; + var result = sut.WriteArray("property", a => a.WriteValue(value)).ToString(); Assert.Equal($"{{\"property\":[\"{value:o}\"]}}", result); @@ -134,6 +137,7 @@ namespace Squidex.Infrastructure.Log public void Should_write_date_value() { var value = DateTime.UtcNow; + var result = sut.WriteArray("property", a => a.WriteValue(value)).ToString(); Assert.Equal($"{{\"property\":[\"{value:yyyy-MM-ddTHH:mm:ssZ}\"]}}", result); @@ -150,7 +154,7 @@ namespace Squidex.Infrastructure.Log [Fact] public void Should_write_pretty_json() { - IObjectWriter prettySut = new JsonLogWriter(Formatting.Indented); + var prettySut = new JsonLogWriterFactory(Formatting.Indented).Create(); var result = prettySut.WriteProperty("property", 1.5).ToString(); @@ -160,7 +164,7 @@ namespace Squidex.Infrastructure.Log [Fact] public void Should_write_extra_line_after_object() { - IObjectWriter prettySut = new JsonLogWriter(Formatting.None, true); + var prettySut = new JsonLogWriterFactory(Formatting.None, true).Create(); var result = prettySut.WriteProperty("property", 1.5).ToString(); diff --git a/tests/Squidex.Infrastructure.Tests/Log/SemanticLogAdapterTests.cs b/tests/Squidex.Infrastructure.Tests/Log/SemanticLogAdapterTests.cs index b08b3262d..ec9958df8 100644 --- a/tests/Squidex.Infrastructure.Tests/Log/SemanticLogAdapterTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Log/SemanticLogAdapterTests.cs @@ -38,7 +38,7 @@ namespace Squidex.Infrastructure.Log output = message; }); - log = new Lazy(() => new SemanticLog(channels, new List(), () => new JsonLogWriter())); + log = new Lazy(() => new SemanticLog(channels, new List(), JsonLogWriterFactory.Default())); sut = SemanticLogLoggerProvider.ForTesting(log.Value); } @@ -197,7 +197,7 @@ namespace Squidex.Infrastructure.Log private static string MakeTestCall(Action writer) { - IObjectWriter sut = new JsonLogWriter(); + var sut = JsonLogWriterFactory.Default().Create(); writer(sut); diff --git a/tests/Squidex.Infrastructure.Tests/Log/SemanticLogTests.cs b/tests/Squidex.Infrastructure.Tests/Log/SemanticLogTests.cs index 24593806c..7f82046ff 100644 --- a/tests/Squidex.Infrastructure.Tests/Log/SemanticLogTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Log/SemanticLogTests.cs @@ -21,7 +21,7 @@ namespace Squidex.Infrastructure.Log private readonly List channels = new List(); private readonly Lazy log; private readonly ILogChannel channel = A.Fake(); - private string output; + private string output = string.Empty; public SemanticLog Log { @@ -35,10 +35,29 @@ namespace Squidex.Infrastructure.Log A.CallTo(() => channel.Log(A.Ignored, A.Ignored)) .Invokes((SemanticLogLevel level, string message) => { - output = message; + output += message; }); - log = new Lazy(() => new SemanticLog(channels, appenders, () => new JsonLogWriter())); + log = new Lazy(() => new SemanticLog(channels, appenders, JsonLogWriterFactory.Default())); + } + + [Fact] + public void Should_log_multiple_lines() + { + Log.Log(SemanticLogLevel.Error, w => w.WriteProperty("logMessage", "Msg1")); + Log.Log(SemanticLogLevel.Error, w => w.WriteProperty("logMessage", "Msg2")); + + var expected1 = + LogTest(w => w + .WriteProperty("logLevel", "Error") + .WriteProperty("logMessage", "Msg1")); + + var expected2 = + LogTest(w => w + .WriteProperty("logLevel", "Error") + .WriteProperty("logMessage", "Msg2")); + + Assert.Equal(expected1 + expected2, output); } [Fact] @@ -307,7 +326,7 @@ namespace Squidex.Infrastructure.Log A.CallTo(() => channel1.Log(A.Ignored, A.Ignored)).Throws(exception1); A.CallTo(() => channel2.Log(A.Ignored, A.Ignored)).Throws(exception2); - var sut = new SemanticLog(new[] { channel1, channel2 }, Enumerable.Empty(), () => new JsonLogWriter()); + var sut = new SemanticLog(new[] { channel1, channel2 }, Enumerable.Empty(), JsonLogWriterFactory.Default()); try { @@ -324,7 +343,7 @@ namespace Squidex.Infrastructure.Log private static string LogTest(Action writer) { - IObjectWriter sut = new JsonLogWriter(); + var sut = JsonLogWriterFactory.Default().Create(); writer(sut); diff --git a/tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs b/tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs index 543d74468..c8ae76adf 100644 --- a/tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs +++ b/tests/Squidex.Infrastructure.Tests/MongoDb/BsonConverterTests.cs @@ -117,44 +117,6 @@ namespace Squidex.Infrastructure.MongoDb private readonly TestObject source = TestObject.CreateWithValues(); private readonly JsonSerializer serializer = JsonSerializer.CreateDefault(); - [Fact] - public void Should_serialize_and_deserialize_to_bson_with_json() - { - var target = JObject.FromObject(source).ToBson().ToJson().ToObject(); - - target.Should().BeEquivalentTo(source); - } - - [Fact] - public void Should_serialize_datetime_to_iso() - { - source.DateTime = new DateTime(2012, 12, 12, 12, 12, 12, DateTimeKind.Utc); - - var target = JObject.FromObject(source).ToBson(); - - Assert.Equal("2012-12-12T12:12:12Z", target["DateTime"].ToString()); - } - - [Fact] - public void Should_serialize_datetimeoffset_to_iso_utc() - { - source.DateTimeOffset = new DateTime(2012, 12, 12, 12, 12, 12, DateTimeKind.Utc); - - var target = JObject.FromObject(source).ToBson(); - - Assert.Equal("2012-12-12T12:12:12Z", target["DateTimeOffset"].ToString()); - } - - [Fact] - public void Should_serialize_datetimeoffset_to_iso_utc_with_offset() - { - source.DateTimeOffset = new DateTimeOffset(2012, 12, 12, 12, 12, 12, TimeSpan.FromHours(2)); - - var target = JObject.FromObject(source).ToBson(); - - Assert.Equal("2012-12-12T12:12:12+02:00", target["DateTimeOffset"].ToString()); - } - [Fact] public void Should_write_problematic_object() { diff --git a/tests/Squidex.Infrastructure.Tests/NamedIdTests.cs b/tests/Squidex.Infrastructure.Tests/NamedIdTests.cs index 09a9d1548..44a09138a 100644 --- a/tests/Squidex.Infrastructure.Tests/NamedIdTests.cs +++ b/tests/Squidex.Infrastructure.Tests/NamedIdTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using Squidex.Infrastructure.Json; using Squidex.Infrastructure.TestHelpers; using Xunit; @@ -41,49 +40,23 @@ namespace Squidex.Infrastructure var id1 = Guid.NewGuid(); var id2 = Guid.NewGuid(); - var token1a = NamedId.Of(id1, "my-name1"); - var token1b = NamedId.Of(id1, "my-name1"); - var token1c = NamedId.Of(id1, "my-name2"); - var token2a = NamedId.Of(id2, "my-name1"); + var named_id1_name1_a = NamedId.Of(id1, "name1"); + var named_id1_name1_b = NamedId.Of(id1, "name1"); - Assert.True(token1a.Equals(token1b)); + var named_id2_name1 = NamedId.Of(id2, "name1"); + var named_id1_name2 = NamedId.Of(id1, "name2"); - Assert.False(token1a.Equals(token2a)); - Assert.False(token1a.Equals(token1c)); - } - - [Fact] - public void Should_make_correct_object_equal_comparisons() - { - var id1 = Guid.NewGuid(); - var id2 = Guid.NewGuid(); - - object token1a = NamedId.Of(id1, "my-name1"); - object token1b = NamedId.Of(id1, "my-name1"); - object token1c = NamedId.Of(id1, "my-name2"); - object token2a = NamedId.Of(id2, "my-name1"); - - Assert.True(token1a.Equals(token1b)); - - Assert.False(token1a.Equals(token2a)); - Assert.False(token1a.Equals(token1c)); - } - - [Fact] - public void Should_provide_correct_hash_codes() - { - var id1 = Guid.NewGuid(); - var id2 = Guid.NewGuid(); - - object token1a = NamedId.Of(id1, "my-name1"); - object token1b = NamedId.Of(id1, "my-name1"); - object token1c = NamedId.Of(id1, "my-name2"); - object token2a = NamedId.Of(id2, "my-name1"); + Assert.Equal(named_id1_name1_a, named_id1_name1_b); + Assert.Equal(named_id1_name1_a.GetHashCode(), named_id1_name1_b.GetHashCode()); + Assert.True(named_id1_name1_a.Equals((object)named_id1_name1_b)); - Assert.Equal(token1a.GetHashCode(), token1b.GetHashCode()); + Assert.NotEqual(named_id1_name1_a, named_id2_name1); + Assert.NotEqual(named_id1_name1_a.GetHashCode(), named_id2_name1.GetHashCode()); + Assert.False(named_id1_name1_a.Equals((object)named_id2_name1)); - Assert.NotEqual(token1a.GetHashCode(), token2a.GetHashCode()); - Assert.NotEqual(token1a.GetHashCode(), token1c.GetHashCode()); + Assert.NotEqual(named_id1_name1_a, named_id1_name2); + Assert.NotEqual(named_id1_name1_a.GetHashCode(), named_id1_name2.GetHashCode()); + Assert.False(named_id1_name1_a.Equals((object)named_id1_name2)); } [Fact] @@ -91,7 +64,9 @@ namespace Squidex.Infrastructure { NamedId value = null; - value.SerializeAndDeserialize(new NamedGuidIdConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } [Fact] @@ -99,7 +74,9 @@ namespace Squidex.Infrastructure { var value = NamedId.Of(Guid.NewGuid(), "my-name"); - value.SerializeAndDeserialize(new NamedGuidIdConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } [Fact] @@ -107,7 +84,9 @@ namespace Squidex.Infrastructure { NamedId value = null; - value.SerializeAndDeserialize(new NamedLongIdConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } [Fact] @@ -115,7 +94,9 @@ namespace Squidex.Infrastructure { var value = NamedId.Of(123L, "my-name"); - value.SerializeAndDeserialize(new NamedLongIdConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } [Fact] @@ -123,7 +104,9 @@ namespace Squidex.Infrastructure { NamedId value = null; - value.SerializeAndDeserialize(new NamedStringIdConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } [Fact] @@ -131,27 +114,27 @@ namespace Squidex.Infrastructure { var value = NamedId.Of(Guid.NewGuid().ToString(), "my-name"); - value.SerializeAndDeserialize(new NamedStringIdConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } [Fact] public void Should_throw_exception_if_string_id_is_not_valid() { - JsonHelper.DoesNotDeserialize>("123", new NamedStringIdConverter()); + Assert.ThrowsAny(() => JsonHelper.Deserialize>("123")); } [Fact] public void Should_throw_exception_if_long_id_is_not_valid() { - JsonHelper.DoesNotDeserialize>("123", new NamedLongIdConverter()); - JsonHelper.DoesNotDeserialize>("invalid-long,name", new NamedLongIdConverter()); + Assert.ThrowsAny(() => JsonHelper.Deserialize>("invalid-long,name")); } [Fact] public void Should_throw_exception_if_guid_id_is_not_valid() { - JsonHelper.DoesNotDeserialize>("123", new NamedGuidIdConverter()); - JsonHelper.DoesNotDeserialize>("invalid-guid,name", new NamedGuidIdConverter()); + Assert.ThrowsAny(() => JsonHelper.Deserialize>("invalid-guid,name")); } } } diff --git a/tests/Squidex.Infrastructure.Tests/Orleans/JsonExternalSerializerTests.cs b/tests/Squidex.Infrastructure.Tests/Orleans/JsonExternalSerializerTests.cs index 27e06eb9d..11deb02cc 100644 --- a/tests/Squidex.Infrastructure.Tests/Orleans/JsonExternalSerializerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Orleans/JsonExternalSerializerTests.cs @@ -11,12 +11,18 @@ using System.Collections.Generic; using System.IO; using FakeItEasy; using Orleans.Serialization; +using Squidex.Infrastructure.TestHelpers; using Xunit; namespace Squidex.Infrastructure.Orleans { public class JsonExternalSerializerTests { + public JsonExternalSerializerTests() + { + J.DefaultSerializer = JsonHelper.DefaultSerializer; + } + [Fact] public void Should_not_copy_null() { diff --git a/tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs b/tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs deleted file mode 100644 index d6888f058..000000000 --- a/tests/Squidex.Infrastructure.Tests/PropertiesBagTests.cs +++ /dev/null @@ -1,431 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Globalization; -using System.Linq; -using Microsoft.CSharp.RuntimeBinder; -using NodaTime; -using Squidex.Infrastructure.Json; -using Squidex.Infrastructure.TestHelpers; -using Xunit; - -namespace Squidex.Infrastructure -{ - public class PropertiesBagTests - { - private readonly CultureInfo c = CultureInfo.InvariantCulture; - private readonly PropertiesBag bag = new PropertiesBag(); - private readonly dynamic dynamicBag; - - public PropertiesBagTests() - { - dynamicBag = bag; - } - - [Fact] - public void Should_serialize_and_deserialize_empty_bag() - { - var output = bag.SerializeAndDeserializeAndReturn(new PropertiesBagConverter()); - - Assert.Equal(bag.Count, output.Count); - } - - [Fact] - public void Should_serialize_and_deserialize() - { - var time = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds()); - - bag.Set("Key1", time); - bag.Set("Key2", "MyString"); - bag.Set("Key3", 123L); - bag.Set("Key4", true); - bag.Set("Key5", Guid.NewGuid()); - - var output = bag.SerializeAndDeserializeAndReturn(new PropertiesBagConverter()); - - foreach (var kvp in output.Properties.Take(4)) - { - Assert.Equal(kvp.Value.RawValue, bag[kvp.Key].RawValue); - } - - Assert.Equal(bag["Key5"].ToGuid(c), output["Key5"].ToGuid(c)); - } - - [Fact] - public void Should_return_false_when_renaming_unknown_property() - { - Assert.False(bag.Rename("OldKey", "NewKey")); - } - - [Fact] - public void Should_throw_when_renaming_to_existing_property() - { - bag.Set("NewKey", 1); - - Assert.Throws(() => bag.Rename("OldKey", "NewKey")); - } - - [Fact] - public void Should_throw_when_renaming_to_same_key() - { - Assert.Throws(() => bag.Rename("SameKey", "SameKey")); - } - - [Fact] - public void Should_provide_property_with_new_name_after_rename() - { - bag.Set("OldKey", 123); - - Assert.True(bag.Rename("OldKey", "NewKey")); - Assert.True(bag.Contains("NewKey")); - - Assert.Equal(1, bag.Count); - Assert.Equal(123, bag["NewKey"].ToInt64(c)); - - Assert.False(bag.Contains("OldKey")); - } - - [Fact] - public void Should_calculate_count_correctly() - { - bag.Set("Key1", 1); - bag.Set("Key2", 1); - - Assert.Equal(2, bag.Count); - } - - [Fact] - public void Should_calculate_keys_correctly() - { - bag.Set("Key1", 1); - bag.Set("Key2", 1); - - Assert.Equal(new[] { "Key1", "Key2" }, bag.PropertyNames.ToArray()); - Assert.Equal(new[] { "Key1", "Key2" }, bag.Properties.Keys.ToArray()); - Assert.Equal(new[] { "Key1", "Key2" }, bag.GetDynamicMemberNames().ToArray()); - } - - [Fact] - public void Should_return_correct_value_when_contains_check() - { - Assert.False(bag.Contains("Key")); - - bag.Set("Key", 1); - - Assert.True(bag.Contains("Key")); - Assert.True(bag.Contains("KEY")); - } - - [Fact] - public void Should_returne_false_when_property_to_rename_does_not_exist() - { - Assert.False(bag.Remove("NOTFOUND")); - } - - [Fact] - public void Should_ignore_casing_when_returning() - { - bag.Set("Key", 1); - - Assert.True(bag.Remove("KEY")); - Assert.False(bag.Contains("KEY")); - } - - [Fact] - public void Should_set_value_as_dynamic() - { - dynamicBag.Key = 456; - - Assert.Equal(456, (int)dynamicBag.Key); - } - - [Fact] - public void Should_throw_when_setting_value_with_invalid_type_dynamically() - { - Assert.Throws(() => dynamicBag.Key = (byte)123); - } - - [Fact] - public void Should_throw_when_setting_value_with_invalid_type() - { - Assert.Throws(() => bag.Set("Key", (byte)1)); - } - - [Fact] - public void Should_return_false_when_making_contains_check() - { - Assert.False(dynamicBag.Contains("Key")); - } - - [Fact] - public void Should_provide_default_value_if_not_exists() - { - Assert.Equal(0, (int)dynamicBag.Key); - } - - [Fact] - public void Should_throw_when_parsing_failed() - { - bag.Set("Key", "abc"); - - Assert.Throws(() => bag["Key"].ToInt64(CultureInfo.InvariantCulture)); - } - - [Fact] - public void Should_return_false_when_converter_does_not_exist() - { - bag.Set("Key", "abc"); - - Assert.Throws(() => (TimeSpan)dynamicBag.Key); - } - - [Fact] - public void Should_convert_string_to_numbers() - { - bag.Set("Key", 123); - - AssertNumber(); - } - - [Fact] - public void Should_convert_int_to_numbers() - { - bag.Set("Key", 123); - - AssertNumber(); - } - - [Fact] - public void Should_convert_long_to_numbers() - { - bag.Set("Key", 123L); - - AssertNumber(); - } - - [Fact] - public void Should_throw_when_casting_from_large_long() - { - bag.Set("Key", long.MaxValue); - - Assert.Throws(() => bag["Key"].ToInt32(c)); - } - - [Fact] - public void Should_convert_float_to_number() - { - bag.Set("Key", 123f); - - AssertNumber(); - } - - [Fact] - public void Should_convert_double_to_number() - { - bag.Set("Key", 123d); - - AssertNumber(); - } - - [Fact] - public void Should_throw_when_casting_from_large_doule() - { - bag.Set("Key", double.MaxValue); - - Assert.Equal(float.PositiveInfinity, bag["Key"].ToSingle(c)); - } - - [Fact] - public void Should_convert_from_instant_value() - { - var time = SystemClock.Instance.GetCurrentInstant(); - - bag.Set("Key", time); - - AssertInstant(time); - } - - [Fact] - public void Should_convert_from_instant_string() - { - var time = Instant.FromUnixTimeSeconds(SystemClock.Instance.GetCurrentInstant().ToUnixTimeSeconds()); - - bag.Set("Key", time.ToString()); - - AssertInstant(time); - } - - [Fact] - public void Should_convert_from_guid_value() - { - var id = Guid.NewGuid(); - - bag.Set("Key", id); - - AssertGuid(id); - } - - [Fact] - public void Should_convert_from_guid_string() - { - var id = Guid.NewGuid(); - - bag.Set("Key", id.ToString()); - - AssertGuid(id); - } - - [Fact] - public void Should_convert_from_boolean_value() - { - bag.Set("Key", true); - - AssertBoolean(true); - } - - [Fact] - public void Should_convert_from_boolean_string() - { - bag.Set("Key", "true"); - - AssertBoolean(true); - } - - [Fact] - public void Should_convert_boolean_from_number() - { - bag.Set("Key", 1); - - AssertBoolean(true); - } - - [Fact] - public void Should_convert_boolean_to_truthy_number_string() - { - bag.Set("Key", "1"); - - AssertBoolean(true); - } - - [Fact] - public void Should_convert_boolean_to_falsy_number_string() - { - bag.Set("Key", "0"); - - AssertBoolean(false); - } - - [Fact] - public void Should_provide_value_as_string() - { - bag.Set("Key", "Foo"); - - AssertString("Foo"); - } - - [Fact] - public void Should_provide_null() - { - bag.Set("Key", null); - - AssertNull(); - } - - [Fact] - public void Should_throw_when_converting_instant_to_number() - { - bag.Set("Key", SystemClock.Instance.GetCurrentInstant()); - - Assert.Throws(() => bag["Key"].ToGuid(CultureInfo.InvariantCulture)); - } - - private void AssertNumber() - { - AssertInt32(123); - AssertInt64(123); - AssertSingle(123); - AssertDouble(123); - } - - private void AssertString(string expected) - { - Assert.Equal(expected, bag["Key"].ToString()); - - Assert.Equal(expected, (string)dynamicBag.Key); - } - - private void AssertNull() - { - Assert.Null(bag["Key"].ToString()); - Assert.Null(bag["Key"].RawValue); - } - - private void AssertBoolean(bool expected) - { - Assert.Equal(expected, bag["Key"].ToBoolean(c)); - Assert.Equal(expected, bag["Key"].ToNullableBoolean(c)); - - Assert.Equal(expected, (bool)dynamicBag.Key); - Assert.Equal(expected, (bool?)dynamicBag.Key); - } - - private void AssertInstant(Instant expected) - { - Assert.Equal(expected, bag["Key"].ToInstant(c)); - Assert.Equal(expected, bag["Key"].ToNullableInstant(c).Value); - - Assert.Equal(expected, (Instant)dynamicBag.Key); - Assert.Equal(expected, (Instant?)dynamicBag.Key); - } - - private void AssertGuid(Guid expected) - { - Assert.Equal(expected, bag["Key"].ToGuid(c)); - Assert.Equal(expected, bag["Key"].ToNullableGuid(c)); - - Assert.Equal(expected, (Guid)dynamicBag.Key); - Assert.Equal(expected, (Guid?)dynamicBag.Key); - } - - private void AssertDouble(double expected) - { - Assert.Equal(expected, bag["Key"].ToDouble(c)); - Assert.Equal(expected, bag["Key"].ToNullableDouble(c)); - - Assert.Equal(expected, (double)dynamicBag.Key); - Assert.Equal(expected, (double?)dynamicBag.Key); - } - - private void AssertSingle(float expected) - { - Assert.Equal(expected, bag["Key"].ToSingle(c)); - Assert.Equal(expected, bag["Key"].ToNullableSingle(c)); - - Assert.Equal(expected, (float)dynamicBag.Key); - Assert.Equal(expected, (float?)dynamicBag.Key); - } - - private void AssertInt32(long expected) - { - Assert.Equal(expected, bag["Key"].ToInt64(c)); - Assert.Equal(expected, bag["Key"].ToNullableInt64(c)); - - Assert.Equal(expected, (long)dynamicBag.Key); - Assert.Equal(expected, (long?)dynamicBag.Key); - } - - private void AssertInt64(int expected) - { - Assert.Equal(expected, bag["Key"].ToInt64(c)); - Assert.Equal(expected, bag["Key"].ToNullableInt64(c)); - - Assert.Equal(expected, (int)dynamicBag.Key); - Assert.Equal(expected, (int?)dynamicBag.Key); - } - } -} \ No newline at end of file diff --git a/tests/Squidex.Infrastructure.Tests/RefTokenTests.cs b/tests/Squidex.Infrastructure.Tests/RefTokenTests.cs index b985b7a97..b7ddf5d99 100644 --- a/tests/Squidex.Infrastructure.Tests/RefTokenTests.cs +++ b/tests/Squidex.Infrastructure.Tests/RefTokenTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using Squidex.Infrastructure.Json; using Squidex.Infrastructure.TestHelpers; using Xunit; @@ -71,39 +70,23 @@ namespace Squidex.Infrastructure [Fact] public void Should_make_correct_equal_comparisons() { - var token1a = RefToken.Parse("client:client1"); - var token1b = RefToken.Parse("client:client1"); - var token2a = RefToken.Parse("client:client2"); + var token_type1_id1_a = RefToken.Parse("type1:client1"); + var token_type1_id1_b = RefToken.Parse("type1:client1"); - Assert.True(token1a.Equals(token1b)); + var token_type2_id1 = RefToken.Parse("type2:client1"); + var token_type1_id2 = RefToken.Parse("type1:client2"); - Assert.False(token1a.Equals(token2a)); - } - - [Fact] - public void Should_make_correct_object_equal_comparisons() - { - object token1a = RefToken.Parse("client:client1"); - object token1b = RefToken.Parse("client:client1"); - object token2a = RefToken.Parse("client:client2"); - - Assert.True(token1a.Equals(token1b)); - - Assert.False(token1a.Equals(token2a)); - Assert.False(token1b.Equals(token2a)); - } - - [Fact] - public void Should_provide_correct_hash_codes() - { - var token1a = RefToken.Parse("client:client1"); - var token1b = RefToken.Parse("client:client1"); - var token2a = RefToken.Parse("client:client2"); + Assert.Equal(token_type1_id1_a, token_type1_id1_b); + Assert.Equal(token_type1_id1_a.GetHashCode(), token_type1_id1_b.GetHashCode()); + Assert.True(token_type1_id1_a.Equals((object)token_type1_id1_b)); - Assert.Equal(token1a.GetHashCode(), token1b.GetHashCode()); + Assert.NotEqual(token_type1_id1_a, token_type2_id1); + Assert.NotEqual(token_type1_id1_a.GetHashCode(), token_type2_id1.GetHashCode()); + Assert.False(token_type1_id1_a.Equals((object)token_type2_id1)); - Assert.NotEqual(token1a.GetHashCode(), token2a.GetHashCode()); - Assert.NotEqual(token1b.GetHashCode(), token2a.GetHashCode()); + Assert.NotEqual(token_type1_id1_a, token_type1_id2); + Assert.NotEqual(token_type1_id1_a.GetHashCode(), token_type1_id2.GetHashCode()); + Assert.False(token_type1_id1_a.Equals((object)token_type1_id2)); } [Fact] @@ -111,7 +94,9 @@ namespace Squidex.Infrastructure { RefToken value = null; - value.SerializeAndDeserialize(new RefTokenConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } [Fact] @@ -119,7 +104,9 @@ namespace Squidex.Infrastructure { var value = RefToken.Parse("client:client1"); - value.SerializeAndDeserialize(new RefTokenConverter()); + var serialized = value.SerializeAndDeserialize(); + + Assert.Equal(value, serialized); } } } diff --git a/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs b/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs index 5da0ea0c2..72e37048b 100644 --- a/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs +++ b/tests/Squidex.Infrastructure.Tests/States/PersistenceEventSourcingTests.cs @@ -56,12 +56,12 @@ namespace Squidex.Infrastructure.States [Fact] public async Task Should_ignore_old_events() { - var storedEvent = new StoredEvent("1", "1", 0, new EventData()); + var storedEvent = new StoredEvent("1", "1", 0, new EventData("Type", new EnvelopeHeaders(), "Payload")); A.CallTo(() => eventStore.QueryAsync(key, 0)) .Returns(new List { storedEvent }); - A.CallTo(() => eventDataFormatter.Parse(storedEvent.Data, true)) + A.CallTo(() => eventDataFormatter.Parse(storedEvent.Data, null)) .Throws(new TypeNameNotFoundException()); var persistedEvents = new List(); @@ -251,12 +251,12 @@ namespace Squidex.Infrastructure.States foreach (var @event in events) { - var eventData = new EventData(); + var eventData = new EventData("Type", new EnvelopeHeaders(), "Payload"); var eventStored = new StoredEvent(i.ToString(), i.ToString(), i, eventData); eventsStored.Add(eventStored); - A.CallTo(() => eventDataFormatter.Parse(eventData, true)) + A.CallTo(() => eventDataFormatter.Parse(eventData, null)) .Returns(new Envelope(@event)); i++; diff --git a/tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs b/tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs index d78a249d8..c3b088571 100644 --- a/tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs +++ b/tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs @@ -7,64 +7,53 @@ using System; using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using Xunit; +using Newtonsoft.Json.Converters; +using Squidex.Infrastructure.Json; +using Squidex.Infrastructure.Json.Newtonsoft; namespace Squidex.Infrastructure.TestHelpers { public static class JsonHelper { - public static void SerializeAndDeserialize(this T value, IContractResolver contractResolver) + public static readonly IJsonSerializer DefaultSerializer = CreateSerializer(); + + public static IJsonSerializer CreateSerializer(TypeNameRegistry typeNameRegistry = null) { var serializerSettings = new JsonSerializerSettings { - ContractResolver = contractResolver, - NullValueHandling = NullValueHandling.Include + SerializationBinder = new TypeNameSerializationBinder(typeNameRegistry ?? new TypeNameRegistry()), + + ContractResolver = new ConverterContractResolver( + new ClaimsPrincipalConverter(), + new InstantConverter(), + new EnvelopeHeadersConverter(), + new JsonValueConverter(), + new LanguageConverter(), + new NamedGuidIdConverter(), + new NamedLongIdConverter(), + new NamedStringIdConverter(), + new RefTokenConverter(), + new StringEnumConverter()), + + TypeNameHandling = TypeNameHandling.Auto }; - var result = JsonConvert.SerializeObject(Tuple.Create(value), serializerSettings); - var output = JsonConvert.DeserializeObject>(result, serializerSettings); - - Assert.Equal(value, output.Item1); + return new NewtonsoftJsonSerializer(serializerSettings); } - public static void SerializeAndDeserialize(this T value, JsonConverter converter) + public static T SerializeAndDeserialize(this T value) { - var output = SerializeAndDeserializeAndReturn(value, converter); - - Assert.Equal(value, output); + return DefaultSerializer.Deserialize>(DefaultSerializer.Serialize(Tuple.Create(value))).Item1; } - public static T SerializeAndDeserializeAndReturn(this T value, JsonConverter converter) + public static T Deserialize(string value) { - var serializerSettings = CreateSettings(converter); - - var result = JsonConvert.SerializeObject(Tuple.Create(value), serializerSettings); - var output = JsonConvert.DeserializeObject>(result, serializerSettings); - - return output.Item1; + return DefaultSerializer.Deserialize>($"{{ \"Item1\": \"{value}\" }}").Item1; } - public static void DoesNotDeserialize(string value, JsonConverter converter) + public static T Deserialize(object value) { - var serializerSettings = CreateSettings(converter); - - Assert.ThrowsAny(() => JsonConvert.DeserializeObject>($"{{ \"Item1\": \"{value}\" }}", serializerSettings)); - } - - private static JsonSerializerSettings CreateSettings(JsonConverter converter) - { - var serializerSettings = new JsonSerializerSettings(); - - if (converter != null) - { - serializerSettings.Converters.Add(converter); - } - - serializerSettings.NullValueHandling = NullValueHandling.Include; - serializerSettings.TypeNameHandling = TypeNameHandling.Auto; - - return serializerSettings; + return DefaultSerializer.Deserialize>($"{{ \"Item1\": {value} }}").Item1; } } } diff --git a/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs b/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs index e3ae304e2..f8fd85490 100644 --- a/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs @@ -19,6 +19,7 @@ namespace Squidex.Infrastructure.UsageTracking { private readonly IUsageRepository usageStore = A.Fake(); private readonly ISemanticLog log = A.Fake(); + private readonly string key = Guid.NewGuid().ToString(); private readonly BackgroundUsageTracker sut; public BackgroundUsageTrackerTests() @@ -31,7 +32,7 @@ namespace Squidex.Infrastructure.UsageTracking { sut.Dispose(); - return Assert.ThrowsAsync(() => sut.TrackAsync("MyKey1", "category1", 1, 1000)); + return Assert.ThrowsAsync(() => sut.TrackAsync(key, "category1", 1, 1000)); } [Fact] @@ -39,7 +40,7 @@ namespace Squidex.Infrastructure.UsageTracking { sut.Dispose(); - return Assert.ThrowsAsync(() => sut.QueryAsync("MyKey1", DateTime.Today, DateTime.Today.AddDays(1))); + return Assert.ThrowsAsync(() => sut.QueryAsync(key, DateTime.Today, DateTime.Today.AddDays(1))); } [Fact] @@ -47,7 +48,7 @@ namespace Squidex.Infrastructure.UsageTracking { sut.Dispose(); - return Assert.ThrowsAsync(() => sut.GetMonthlyCallsAsync("MyKey1", DateTime.Today)); + return Assert.ThrowsAsync(() => sut.GetMonthlyCallsAsync(key, DateTime.Today)); } [Fact] @@ -63,10 +64,10 @@ namespace Squidex.Infrastructure.UsageTracking new StoredUsage("category1", date.AddDays(7), Counters(17, 22)) }; - A.CallTo(() => usageStore.QueryAsync("MyKey1", new DateTime(2016, 1, 1), new DateTime(2016, 1, 31))) + A.CallTo(() => usageStore.QueryAsync($"{key}_API", new DateTime(2016, 1, 1), new DateTime(2016, 1, 31))) .Returns(originalData); - var result = await sut.GetMonthlyCallsAsync("MyKey1", date); + var result = await sut.GetMonthlyCallsAsync(key, date); Assert.Equal(55, result); } @@ -86,10 +87,10 @@ namespace Squidex.Infrastructure.UsageTracking new StoredUsage(null, f.AddDays(2), Counters(11, 14)) }; - A.CallTo(() => usageStore.QueryAsync("MyKey1", f, t)) + A.CallTo(() => usageStore.QueryAsync($"{key}_API", f, t)) .Returns(originalData); - var result = await sut.QueryAsync("MyKey1", f, t); + var result = await sut.QueryAsync(key, f, t); var expected = new Dictionary> { @@ -120,10 +121,10 @@ namespace Squidex.Infrastructure.UsageTracking var f = DateTime.Today; var t = DateTime.Today.AddDays(4); - A.CallTo(() => usageStore.QueryAsync("MyKey1", f, t)) + A.CallTo(() => usageStore.QueryAsync($"{key}_API", f, t)) .Returns(new List()); - var result = await sut.QueryAsync("MyKey1", f, t); + var result = await sut.QueryAsync(key, f, t); var expected = new Dictionary> { @@ -143,8 +144,8 @@ namespace Squidex.Infrastructure.UsageTracking [Fact] public async Task Should_not_track_if_weight_less_than_zero() { - await sut.TrackAsync("MyKey1", "MyCategory", -1, 1000); - await sut.TrackAsync("MyKey1", "MyCategory", 0, 1000); + await sut.TrackAsync(key, "MyCategory", -1, 1000); + await sut.TrackAsync(key, "MyCategory", 0, 1000); sut.Next(); sut.Dispose(); @@ -156,18 +157,22 @@ namespace Squidex.Infrastructure.UsageTracking [Fact] public async Task Should_aggregate_and_store_on_dispose() { + var key1 = Guid.NewGuid().ToString(); + var key2 = Guid.NewGuid().ToString(); + var key3 = Guid.NewGuid().ToString(); + var today = DateTime.Today; - await sut.TrackAsync("MyKey1", "MyCategory1", 1, 1000); + await sut.TrackAsync(key1, "MyCategory1", 1, 1000); - await sut.TrackAsync("MyKey2", "MyCategory1", 1.0, 2000); - await sut.TrackAsync("MyKey2", "MyCategory1", 0.5, 3000); + await sut.TrackAsync(key2, "MyCategory1", 1.0, 2000); + await sut.TrackAsync(key2, "MyCategory1", 0.5, 3000); - await sut.TrackAsync("MyKey3", "MyCategory1", 0.3, 4000); - await sut.TrackAsync("MyKey3", "MyCategory1", 0.1, 5000); + await sut.TrackAsync(key3, "MyCategory1", 0.3, 4000); + await sut.TrackAsync(key3, "MyCategory1", 0.1, 5000); - await sut.TrackAsync("MyKey3", null, 0.5, 2000); - await sut.TrackAsync("MyKey3", null, 0.5, 6000); + await sut.TrackAsync(key3, null, 0.5, 2000); + await sut.TrackAsync(key3, null, 0.5, 6000); UsageUpdate[] updates = null; @@ -179,10 +184,10 @@ namespace Squidex.Infrastructure.UsageTracking updates.Should().BeEquivalentTo(new[] { - new UsageUpdate(today, "MyKey1", "MyCategory1", Counters(1.0, 1000)), - new UsageUpdate(today, "MyKey2", "MyCategory1", Counters(1.5, 5000)), - new UsageUpdate(today, "MyKey3", "MyCategory1", Counters(0.4, 9000)), - new UsageUpdate(today, "MyKey3", "*", Counters(1, 8000)) + new UsageUpdate(today, $"{key1}_API", "MyCategory1", Counters(1.0, 1000)), + new UsageUpdate(today, $"{key2}_API", "MyCategory1", Counters(1.5, 5000)), + new UsageUpdate(today, $"{key3}_API", "MyCategory1", Counters(0.4, 9000)), + new UsageUpdate(today, $"{key3}_API", "*", Counters(1, 8000)) }, o => o.ComparingByMembers()); A.CallTo(() => usageStore.TrackUsagesAsync(A.Ignored)) diff --git a/tests/Squidex.Infrastructure.Tests/UsageTracking/ThreadingUsageTrackerTests.cs b/tests/Squidex.Infrastructure.Tests/UsageTracking/CachingUsageTrackerTests.cs similarity index 64% rename from tests/Squidex.Infrastructure.Tests/UsageTracking/ThreadingUsageTrackerTests.cs rename to tests/Squidex.Infrastructure.Tests/UsageTracking/CachingUsageTrackerTests.cs index 4a3bc30c9..4ceb57a93 100644 --- a/tests/Squidex.Infrastructure.Tests/UsageTracking/ThreadingUsageTrackerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/UsageTracking/CachingUsageTrackerTests.cs @@ -14,13 +14,14 @@ using Xunit; namespace Squidex.Infrastructure.UsageTracking { - public class ThreadingUsageTrackerTests + public class CachingUsageTrackerTests { private readonly MemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions())); + private readonly string key = Guid.NewGuid().ToString(); private readonly IUsageTracker inner = A.Fake(); private readonly IUsageTracker sut; - public ThreadingUsageTrackerTests() + public CachingUsageTrackerTests() { sut = new CachingUsageTracker(inner, cache); } @@ -28,34 +29,34 @@ namespace Squidex.Infrastructure.UsageTracking [Fact] public async Task Should_forward_track_call() { - await sut.TrackAsync("MyKey", "MyCategory", 123, 456); + await sut.TrackAsync(key, "MyCategory", 123, 456); - A.CallTo(() => inner.TrackAsync("MyKey", "MyCategory", 123, 456)) + A.CallTo(() => inner.TrackAsync(key, "MyCategory", 123, 456)) .MustHaveHappened(); } [Fact] public async Task Should_forward_query_call() { - await sut.QueryAsync("MyKey", DateTime.MaxValue, DateTime.MinValue); + await sut.QueryAsync(key, DateTime.MaxValue, DateTime.MinValue); - A.CallTo(() => inner.QueryAsync("MyKey", DateTime.MaxValue, DateTime.MinValue)) + A.CallTo(() => inner.QueryAsync(key, DateTime.MaxValue, DateTime.MinValue)) .MustHaveHappened(); } [Fact] public async Task Should_cache_monthly_usage() { - A.CallTo(() => inner.GetMonthlyCallsAsync("MyKey", DateTime.Today)) + A.CallTo(() => inner.GetMonthlyCallsAsync(key, DateTime.Today)) .Returns(100); - var result1 = await sut.GetMonthlyCallsAsync("MyKey", DateTime.Today); - var result2 = await sut.GetMonthlyCallsAsync("MyKey", DateTime.Today); + var result1 = await sut.GetMonthlyCallsAsync(key, DateTime.Today); + var result2 = await sut.GetMonthlyCallsAsync(key, DateTime.Today); Assert.Equal(100, result1); Assert.Equal(100, result2); - A.CallTo(() => inner.GetMonthlyCallsAsync("MyKey", DateTime.Today)) + A.CallTo(() => inner.GetMonthlyCallsAsync(key, DateTime.Today)) .MustHaveHappened(Repeated.Exactly.Once); } } diff --git a/tools/Migrate_01/MigrationPath.cs b/tools/Migrate_01/MigrationPath.cs index 4573d5e06..4cf8354a0 100644 --- a/tools/Migrate_01/MigrationPath.cs +++ b/tools/Migrate_01/MigrationPath.cs @@ -16,7 +16,7 @@ namespace Migrate_01 { public sealed class MigrationPath : IMigrationPath { - private const int CurrentVersion = 12; + private const int CurrentVersion = 13; private readonly IServiceProvider serviceProvider; public MigrationPath(IServiceProvider serviceProvider) @@ -78,6 +78,12 @@ namespace Migrate_01 yield return serviceProvider.GetRequiredService(); } + // Version 13: Json refactoring + if (version < 13) + { + yield return serviceProvider.GetRequiredService(); + } + // Version 01: Introduce app patterns. if (version < 1) { diff --git a/tools/Migrate_01/Migrations/AddPatterns.cs b/tools/Migrate_01/Migrations/AddPatterns.cs index 4426982c6..14dd415ee 100644 --- a/tools/Migrate_01/Migrations/AddPatterns.cs +++ b/tools/Migrate_01/Migrations/AddPatterns.cs @@ -24,6 +24,7 @@ namespace Migrate_01.Migrations public AddPatterns(InitialPatterns initialPatterns, IGrainFactory grainFactory) { this.initialPatterns = initialPatterns; + this.grainFactory = grainFactory; } diff --git a/tools/Migrate_01/Migrations/ConvertEventStore.cs b/tools/Migrate_01/Migrations/ConvertEventStore.cs index 37c6a476d..4a4fc7e7f 100644 --- a/tools/Migrate_01/Migrations/ConvertEventStore.cs +++ b/tools/Migrate_01/Migrations/ConvertEventStore.cs @@ -12,7 +12,6 @@ using MongoDB.Driver; using Newtonsoft.Json.Linq; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Migrations; -using Squidex.Infrastructure.MongoDb; namespace Migrate_01.Migrations { diff --git a/tools/Migrate_01/Migrations/ConvertRuleEventsJson.cs b/tools/Migrate_01/Migrations/ConvertRuleEventsJson.cs new file mode 100644 index 000000000..b543acddf --- /dev/null +++ b/tools/Migrate_01/Migrations/ConvertRuleEventsJson.cs @@ -0,0 +1,36 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Threading.Tasks; +using MongoDB.Bson; +using MongoDB.Driver; +using Squidex.Infrastructure.Migrations; + +namespace Migrate_01.Migrations +{ + public sealed class ConvertRuleEventsJson : IMigration + { + private readonly IMongoCollection collection; + + public ConvertRuleEventsJson(IMongoDatabase database) + { + collection = database.GetCollection("RuleEvents"); + } + + public async Task UpdateAsync() + { + foreach (var document in collection.Find(new BsonDocument()).ToEnumerable()) + { + document["Job"]["actionData"] = document["Job"]["actionData"].ToBsonDocument().ToJson(); + + var filter = Builders.Filter.Eq("_id", document["_id"].ToString()); + + await collection.ReplaceOneAsync(filter, document); + } + } + } +} diff --git a/tools/Migrate_01/Rebuilder.cs b/tools/Migrate_01/Rebuilder.cs index be45577c0..d3bac5427 100644 --- a/tools/Migrate_01/Rebuilder.cs +++ b/tools/Migrate_01/Rebuilder.cs @@ -25,6 +25,7 @@ using Squidex.Infrastructure; using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing; +using Squidex.Infrastructure.Json; using Squidex.Infrastructure.States; namespace Migrate_01 @@ -33,18 +34,21 @@ namespace Migrate_01 { private readonly FieldRegistry fieldRegistry; private readonly ILocalCache localCache; + private readonly IJsonSerializer serializer; private readonly IStore store; private readonly IEventStore eventStore; public Rebuilder( FieldRegistry fieldRegistry, ILocalCache localCache, + IJsonSerializer serializer, IStore store, IEventStore eventStore) { this.fieldRegistry = fieldRegistry; this.eventStore = eventStore; this.localCache = localCache; + this.serializer = serializer; this.store = store; } @@ -104,7 +108,9 @@ namespace Migrate_01 await eventStore.QueryAsync(async storedEvent => { - var id = Guid.Parse(storedEvent.Data.Metadata.Value(CommonHeaders.AggregateId)); + var headers = storedEvent.Data.Headers; + + var id = headers.AggregateId(); if (handledIds.Add(id)) {