Browse Source

Merge branch 'master' into nswag

# Conflicts:
#	src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
#	src/Squidex/Areas/Api/Config/Swagger/SwaggerExtensions.cs
#	src/Squidex/Areas/Api/Config/Swagger/SwaggerServices.cs
#	src/Squidex/Squidex.csproj
pull/336/head
Sebastian Stehle 7 years ago
parent
commit
533f9be771
  1. 5
      .drone.yml
  2. 4
      extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs
  3. 19
      extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs
  4. 14
      extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs
  5. 53
      extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs
  6. 9
      extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs
  7. 8
      extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs
  8. 39
      src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs
  9. 14
      src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs
  10. 30
      src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs
  11. 11
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs
  12. 6
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs
  13. 12
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs
  14. 2
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs
  15. 10
      src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs
  16. 4
      src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs
  17. 17
      src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs
  18. 32
      src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs
  19. 4
      src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs
  20. 22
      src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs
  21. 68
      src/Squidex.Domain.Apps.Core.Model/DictionaryWrapper{TKey,TValue}.cs
  22. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs
  23. 5
      src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs
  24. 4
      src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs
  25. 11
      src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs
  26. 6
      src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs
  27. 12
      src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs
  28. 8
      src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs
  29. 8
      src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs
  30. 29
      src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs
  31. 4
      src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs
  32. 8
      src/Squidex.Domain.Apps.Core.Model/Schemas/FieldRegistry.cs
  33. 164
      src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs
  34. 8
      src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs
  35. 8
      src/Squidex.Domain.Apps.Core.Model/Schemas/IField.cs
  36. 18
      src/Squidex.Domain.Apps.Core.Model/Schemas/IFieldSettings.cs
  37. 25
      src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonFieldModel.cs
  38. 12
      src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs
  39. 74
      src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs
  40. 14
      src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs
  41. 8
      src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs
  42. 9
      src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs
  43. 8
      src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs
  44. 14
      src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs
  45. 8
      src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs
  46. 9
      src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs
  47. 8
      src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs
  48. 14
      src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs
  49. 16
      src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldEditor.cs
  50. 13
      src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs
  51. 19
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs
  52. 6
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs
  53. 17
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
  54. 4
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs
  55. 22
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs
  56. 11
      src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs
  57. 50
      src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs
  58. 4
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs
  59. 36
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
  60. 27
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs
  61. 12
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs
  62. 4
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs
  63. 6
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs
  64. 7
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs
  65. 28
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs
  66. 38
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  67. 21
      src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  68. 8
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs
  69. 94
      src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs
  70. 1
      src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  71. 34
      src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs
  72. 5
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs
  73. 175
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs
  74. 46
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs
  75. 8
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs
  76. 7
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs
  77. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs
  78. 15
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs
  79. 51
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs
  80. 23
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs
  81. 13
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Extensions.cs
  82. 13
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  83. 19
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs
  84. 9
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  85. 7
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs
  86. 15
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  87. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  88. 50
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs
  89. 66
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs
  90. 18
      src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs
  91. 19
      src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs
  92. 6
      src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs
  93. 8
      src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs
  94. 18
      src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs
  95. 4
      src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs
  96. 6
      src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs
  97. 7
      src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs
  98. 7
      src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs
  99. 24
      src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs
  100. 7
      src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs

5
.drone.yml

@ -18,12 +18,15 @@ pipeline:
image: docker image: docker
commands: commands:
- docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD - 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
- docker push squidex/squidex:dev-$BUILD_NUMBER
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker:/var/lib/docker - /var/lib/docker:/var/lib/docker
secrets: [ docker_username, docker_password ] secrets: [ docker_username, docker_password ]
environment:
- BUILD_NUMBER=${DRONE_BUILD_NUMBER}
when: when:
event: push event: push
branch: [ master ] branch: [ master ]

4
extensions/Squidex.Extensions/Actions/Algolia/AlgoliaActionHandler.cs

@ -56,7 +56,9 @@ namespace Squidex.Extensions.Actions.Algolia
{ {
ruleDescription = $"Add entry to Algolia index: {action.IndexName}"; 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; ruleJob.Content["objectID"] = contentId;
} }

19
extensions/Squidex.Extensions/Actions/Discourse/DiscourseActionHandler.cs

@ -6,10 +6,10 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; 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 url = $"{action.Url.ToString().TrimEnd('/')}/posts.json?api_key={action.ApiKey}&api_username={action.ApiUsername}";
var json = var json = new Dictionary<string, object>
new JObject( {
new JProperty("raw", Format(action.Text, @event)), ["raw"] = Format(action.Text, @event),
new JProperty("title", Format(action.Title, @event))); ["title"] = Format(action.Title, @event)
};
if (action.Topic.HasValue) if (action.Topic.HasValue)
{ {
json.Add(new JProperty("topic_id", action.Topic.Value)); json.Add("topic_id", action.Topic.Value);
} }
if (action.Category.HasValue) 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 var ruleJob = new DiscourseJob
{ {
RequestUrl = url, RequestUrl = url,
RequestBody = json.ToString() RequestBody = requestBody
}; };
var description = var description =

14
extensions/Squidex.Extensions/Actions/ElasticSearch/ElasticSearchActionHandler.cs

@ -8,7 +8,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Elasticsearch.Net; using Elasticsearch.Net;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
@ -60,8 +59,9 @@ namespace Squidex.Extensions.Actions.ElasticSearch
{ {
ruleDescription = $"Upsert to index: {action.IndexName}"; ruleDescription = $"Upsert to index: {action.IndexName}";
ruleJob.Content = ToPayload(contentEvent); var json = ToJson(contentEvent);
ruleJob.Content["objectID"] = contentId;
ruleJob.Content = $"{{ \"objectId\": \"{contentId}\", {json.Substring(1)}";
} }
ruleJob.Username = action.Username; ruleJob.Username = action.Username;
@ -86,9 +86,7 @@ namespace Squidex.Extensions.Actions.ElasticSearch
{ {
if (job.Content != null) if (job.Content != null)
{ {
var doc = job.Content.ToString(); var response = await client.IndexAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId, job.Content);
var response = await client.IndexAsync<StringResponse>(job.IndexName, job.IndexType, job.ContentId, doc);
return (response.Body, response.OriginalException); return (response.Body, response.OriginalException);
} }
@ -116,10 +114,10 @@ namespace Squidex.Extensions.Actions.ElasticSearch
public string ContentId { get; set; } public string ContentId { get; set; }
public string Content { get; set; }
public string IndexName { get; set; } public string IndexName { get; set; }
public string IndexType { get; set; } public string IndexType { get; set; }
public JObject Content { get; set; }
} }
} }

53
extensions/Squidex.Extensions/Actions/Medium/MediumActionHandler.cs

@ -9,11 +9,10 @@ using System;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure.Http; using Squidex.Infrastructure.Http;
using Squidex.Infrastructure.Json;
namespace Squidex.Extensions.Actions.Medium namespace Squidex.Extensions.Actions.Medium
{ {
@ -22,53 +21,61 @@ namespace Squidex.Extensions.Actions.Medium
private const string Description = "Post to medium"; private const string Description = "Post to medium";
private readonly IHttpClientFactory httpClientFactory; 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) : base(formatter)
{ {
this.httpClientFactory = httpClientFactory; this.httpClientFactory = httpClientFactory;
this.serializer = serializer;
} }
protected override (string Description, MediumJob Data) CreateJob(EnrichedEvent @event, MediumAction action) protected override (string Description, MediumJob Data) CreateJob(EnrichedEvent @event, MediumAction action)
{ {
var requestBody = var ruleJob = new MediumJob { AccessToken = action.AccessToken, PublicationId = action.PublicationId };
new JObject(
new JProperty("title", Format(action.Title, @event)), var requestBody = new
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
{ {
AccessToken = action.AccessToken, title = Format(action.Title, @event),
PublicationId = action.PublicationId, contentFormat = action.IsHtml ? "html" : "markdown",
RequestBody = requestBody.ToString(Formatting.Indented) content = Format(action.Content, @event),
canonicalUrl = Format(action.CanonicalUrl, @event),
tags = ParseTags(@event, action)
}; };
ruleJob.RequestBody = ToJson(requestBody);
return (Description, ruleJob); return (Description, ruleJob);
} }
private JArray ParseTags(EnrichedEvent @event, MediumAction action) private string[] ParseTags(EnrichedEvent @event, MediumAction action)
{ {
if (string.IsNullOrWhiteSpace(action.Tags)) if (string.IsNullOrWhiteSpace(action.Tags))
{ {
return null; return null;
} }
string[] tags;
try try
{ {
var jsonTags = Format(action.Tags, @event); var jsonTags = Format(action.Tags, @event);
tags = JsonConvert.DeserializeObject<string[]>(jsonTags); return serializer.Deserialize<string[]>(jsonTags);
} }
catch catch
{ {
tags = action.Tags.Split(','); return action.Tags.Split(',');
} }
return new JArray(tags);
} }
protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(MediumJob job) 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); response = await httpClient.SendAsync(meRequest);
var responseString = await response.Content.ReadAsStringAsync(); var responseString = await response.Content.ReadAsStringAsync();
var responseJson = JToken.Parse(responseString); var responseJson = serializer.Deserialize<UserResponse>(responseString);
var id = responseJson["data"]["id"].ToString(); var id = responseJson.Data?.Id;
path = $"v1/users/{id}/posts"; path = $"v1/users/{id}/posts";
} }

9
extensions/Squidex.Extensions/Actions/Prerender/PrerenderActionHandler.cs

@ -9,7 +9,6 @@ using System;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
@ -29,12 +28,10 @@ namespace Squidex.Extensions.Actions.Prerender
{ {
var url = Format(action.Url, @event); var url = Format(action.Url, @event);
var request = var request = new { prerenderToken = action.Token, url };
new JObject( var requestBody = ToJson(request);
new JProperty("prerenderToken", action.Token),
new JProperty("url", url));
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) protected override async Task<(string Dump, Exception Exception)> ExecuteJobAsync(PrerenderJob job)

8
extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs

@ -9,8 +9,6 @@ using System;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -33,14 +31,12 @@ namespace Squidex.Extensions.Actions.Slack
protected override (string Description, SlackJob Data) CreateJob(EnrichedEvent @event, SlackAction action) protected override (string Description, SlackJob Data) CreateJob(EnrichedEvent @event, SlackAction action)
{ {
var body = var body = new { text = Format(action.Text, @event) };
new JObject(
new JProperty("text", Format(action.Text, @event)));
var ruleJob = new SlackJob var ruleJob = new SlackJob
{ {
RequestUrl = action.WebhookUrl.ToString(), RequestUrl = action.WebhookUrl.ToString(),
RequestBody = body.ToString(Formatting.Indented) RequestBody = ToJson(body)
}; };
return (Description, ruleJob); return (Description, ruleJob);

39
src/Squidex.Domain.Apps.Core.Model/Apps/AppClients.cs

@ -5,49 +5,60 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable; using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
public sealed class AppClients : DictionaryWrapper<string, AppClient> public sealed class AppClients : ArrayDictionary<string, AppClient>
{ {
public static readonly AppClients Empty = new AppClients(); public static readonly AppClients Empty = new AppClients();
private AppClients() private AppClients()
: base(ImmutableDictionary<string, AppClient>.Empty)
{ {
} }
public AppClients(ImmutableDictionary<string, AppClient> inner) public AppClients(KeyValuePair<string, AppClient>[] items)
: base(inner) : base(items)
{ {
} }
[Pure] [Pure]
public AppClients Add(string id, AppClient client) public AppClients Revoke(string id)
{ {
Guard.NotNullOrEmpty(id, nameof(id)); Guard.NotNullOrEmpty(id, nameof(id));
Guard.NotNull(client, nameof(client));
return new AppClients(Inner.Add(id, client)); return new AppClients(Without(id));
} }
[Pure] [Pure]
public AppClients Add(string id, string secret) public AppClients Add(string id, AppClient client)
{ {
Guard.NotNullOrEmpty(id, nameof(id)); 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] [Pure]
public AppClients Revoke(string id) public AppClients Add(string id, string secret)
{ {
Guard.NotNullOrEmpty(id, nameof(id)); 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] [Pure]
@ -60,7 +71,7 @@ namespace Squidex.Domain.Apps.Core.Apps
return this; return this;
} }
return new AppClients(Inner.SetItem(id, client.Rename(newName))); return new AppClients(With(id, client.Rename(newName)));
} }
[Pure] [Pure]
@ -73,7 +84,7 @@ namespace Squidex.Domain.Apps.Core.Apps
return this; return this;
} }
return new AppClients(Inner.SetItem(id, client.Update(role))); return new AppClients(With(id, client.Update(role)));
} }
} }
} }

14
src/Squidex.Domain.Apps.Core.Model/Apps/AppContributors.cs

@ -5,23 +5,23 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable; using System.Collections.Generic;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
public sealed class AppContributors : DictionaryWrapper<string, string> public sealed class AppContributors : ArrayDictionary<string, string>
{ {
public static readonly AppContributors Empty = new AppContributors(); public static readonly AppContributors Empty = new AppContributors();
private AppContributors() private AppContributors()
: base(ImmutableDictionary<string, string>.Empty)
{ {
} }
public AppContributors(ImmutableDictionary<string, string> inner) public AppContributors(KeyValuePair<string, string>[] items)
: base(inner) : base(items)
{ {
} }
@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.Apps
Guard.NotNullOrEmpty(contributorId, nameof(contributorId)); Guard.NotNullOrEmpty(contributorId, nameof(contributorId));
Guard.NotNullOrEmpty(role, nameof(role)); Guard.NotNullOrEmpty(role, nameof(role));
return new AppContributors(Inner.SetItem(contributorId, role)); return new AppContributors(With(contributorId, role));
} }
[Pure] [Pure]
@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Core.Apps
{ {
Guard.NotNullOrEmpty(contributorId, nameof(contributorId)); Guard.NotNullOrEmpty(contributorId, nameof(contributorId));
return new AppContributors(Inner.Remove(contributorId)); return new AppContributors(Without(contributorId));
} }
} }
} }

30
src/Squidex.Domain.Apps.Core.Model/Apps/AppPatterns.cs

@ -4,39 +4,45 @@
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Immutable; using System.Collections.Generic;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
public sealed class AppPatterns : DictionaryWrapper<Guid, AppPattern> public sealed class AppPatterns : ArrayDictionary<Guid, AppPattern>
{ {
public static readonly AppPatterns Empty = new AppPatterns(); public static readonly AppPatterns Empty = new AppPatterns();
private AppPatterns() private AppPatterns()
: base(ImmutableDictionary<Guid, AppPattern>.Empty)
{ {
} }
public AppPatterns(ImmutableDictionary<Guid, AppPattern> inner) public AppPatterns(KeyValuePair<Guid, AppPattern>[] items)
: base(inner) : base(items)
{ {
} }
[Pure] [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(Without(id));
return new AppPatterns(Inner.Add(id, newPattern));
} }
[Pure] [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] [Pure]
@ -50,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Apps
return this; return this;
} }
return new AppPatterns(Inner.SetItem(id, appPattern.Update(name, pattern, message))); return new AppPatterns(With(id, appPattern.Update(name, pattern, message)));
} }
} }
} }

11
src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppClientsConverter.cs

@ -7,9 +7,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
@ -31,7 +31,12 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
var json = serializer.Deserialize<Dictionary<string, JsonAppClient>>(reader); var json = serializer.Deserialize<Dictionary<string, JsonAppClient>>(reader);
return new AppClients(json.ToImmutableDictionary(x => x.Key, x => x.Value.ToClient())); return new AppClients(json.Select(Convert).ToArray());
}
private static KeyValuePair<string, AppClient> Convert(KeyValuePair<string, JsonAppClient> kvp)
{
return new KeyValuePair<string, AppClient>(kvp.Key, kvp.Value.ToClient());
} }
} }
} }

6
src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppContributorsConverter.cs

@ -7,9 +7,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
var json = serializer.Deserialize<Dictionary<string, string>>(reader); var json = serializer.Deserialize<Dictionary<string, string>>(reader);
return new AppContributors(json.ToImmutableDictionary()); return new AppContributors(json.ToArray());
} }
} }
} }

12
src/Squidex.Domain.Apps.Core.Model/Apps/Json/AppPatternsConverter.cs

@ -4,11 +4,12 @@
// Copyright (c) Squidex UG (haftungsbeschränkt) // Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
@ -30,7 +31,12 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
var json = serializer.Deserialize<Dictionary<Guid, JsonAppPattern>>(reader); var json = serializer.Deserialize<Dictionary<Guid, JsonAppPattern>>(reader);
return new AppPatterns(json.ToImmutableDictionary(x => x.Key, x => x.Value.ToPattern())); return new AppPatterns(json.Select(Convert).ToArray());
}
private static KeyValuePair<Guid, AppPattern> Convert(KeyValuePair<Guid, JsonAppPattern> kvp)
{
return new KeyValuePair<Guid, AppPattern>(kvp.Key, kvp.Value.ToPattern());
} }
} }
} }

2
src/Squidex.Domain.Apps.Core.Model/Apps/Json/LanguagesConfigConverter.cs

@ -7,7 +7,7 @@
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json namespace Squidex.Domain.Apps.Core.Apps.Json
{ {

10
src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesConverter.cs

@ -6,11 +6,10 @@
// ========================================================================== // ==========================================================================
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
namespace Squidex.Domain.Apps.Core.Apps.Json namespace Squidex.Domain.Apps.Core.Apps.Json
@ -33,7 +32,12 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
var json = serializer.Deserialize<Dictionary<string, string[]>>(reader); var json = serializer.Deserialize<Dictionary<string, string[]>>(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<string, Role> Convert(KeyValuePair<string, string[]> kvp)
{
return new KeyValuePair<string, Role>(kvp.Key, new Role(kvp.Key, new PermissionSet(kvp.Value)));
} }
} }
} }

4
src/Squidex.Domain.Apps.Core.Model/Apps/LanguageConfig.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -13,7 +14,6 @@ namespace Squidex.Domain.Apps.Core.Apps
{ {
public sealed class LanguageConfig : IFieldPartitionItem public sealed class LanguageConfig : IFieldPartitionItem
{ {
private static readonly Language[] DefaultFallback = new Language[0];
private readonly Language language; private readonly Language language;
private readonly Language[] languageFallbacks; private readonly Language[] languageFallbacks;
@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Apps
IsOptional = isOptional; IsOptional = isOptional;
this.language = language; this.language = language;
this.languageFallbacks = fallback ?? DefaultFallback; this.languageFallbacks = fallback ?? Array.Empty<Language>();
} }
} }
} }

17
src/Squidex.Domain.Apps.Core.Model/Apps/LanguagesConfig.cs

@ -8,10 +8,10 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Core.Apps
{ {
public static readonly LanguagesConfig English = Build(Language.EN); public static readonly LanguagesConfig English = Build(Language.EN);
private readonly ImmutableDictionary<Language, LanguageConfig> languages; private readonly ArrayDictionary<Language, LanguageConfig> languages;
private readonly LanguageConfig master; private readonly LanguageConfig master;
public LanguageConfig Master public LanguageConfig Master
@ -47,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.Apps
get { return languages.Count; } get { return languages.Count; }
} }
private LanguagesConfig(ImmutableDictionary<Language, LanguageConfig> languages, LanguageConfig master, bool checkMaster = true) private LanguagesConfig(ArrayDictionary<Language, LanguageConfig> languages, LanguageConfig master, bool checkMaster = true)
{ {
if (checkMaster) if (checkMaster)
{ {
@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Core.Apps
{ {
Guard.NotNull(configs, nameof(configs)); 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) public static LanguagesConfig Build(params LanguageConfig[] configs)
@ -100,7 +100,12 @@ namespace Squidex.Domain.Apps.Core.Apps
{ {
Guard.NotNull(config, nameof(config)); Guard.NotNull(config, nameof(config));
return new LanguagesConfig(languages.SetItem(config.Language, config), Master?.Language == config.Language ? config : Master); var newLanguages =
new ArrayDictionary<Language, LanguageConfig>(languages.With(config.Language, config));
var newMaster = Master?.Language == config.Language ? config : Master;
return new LanguagesConfig(newLanguages, newMaster);
} }
[Pure] [Pure]
@ -114,7 +119,7 @@ namespace Squidex.Domain.Apps.Core.Apps
config.Language, config.Language,
config.IsOptional, config.IsOptional,
config.LanguageFallbacks.Except(new[] { language }))) config.LanguageFallbacks.Except(new[] { language })))
.ToImmutableDictionary(x => x.Language); .ToArrayDictionary(x => x.Language);
var newMaster = var newMaster =
newLanguages.Values.FirstOrDefault(x => x.Language == Master.Language) ?? newLanguages.Values.FirstOrDefault(x => x.Language == Master.Language) ??

32
src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs

@ -6,38 +6,44 @@
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Linq;
namespace Squidex.Domain.Apps.Core.Apps namespace Squidex.Domain.Apps.Core.Apps
{ {
public sealed class Roles : DictionaryWrapper<string, Role> public sealed class Roles : ArrayDictionary<string, Role>
{ {
public static readonly Roles Empty = new Roles(); public static readonly Roles Empty = new Roles();
private Roles() private Roles()
: base(ImmutableDictionary<string, Role>.Empty)
{ {
} }
public Roles(ImmutableDictionary<string, Role> inner) public Roles(KeyValuePair<string, Role>[] items)
: base(inner) : base(items)
{ {
} }
[Pure] [Pure]
public Roles Add(string name) public Roles Remove(string name)
{ {
var newRole = new Role(name); return new Roles(Without(name));
return new Roles(Inner.Add(name, newRole));
} }
[Pure] [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] [Pure]
@ -51,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.Apps
return this; 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) public static Roles CreateDefaults(string app)
@ -63,7 +69,7 @@ namespace Squidex.Domain.Apps.Core.Apps
[Role.Editor] = Role.CreateEditor(app), [Role.Editor] = Role.CreateEditor(app),
[Role.Owner] = Role.CreateOwner(app), [Role.Owner] = Role.CreateOwner(app),
[Role.Reader] = Role.CreateReader(app) [Role.Reader] = Role.CreateReader(app)
}.ToImmutableDictionary()); }.ToArray());
} }
} }
} }

4
src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs

@ -9,7 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Contents namespace Squidex.Domain.Apps.Core.Contents
{ {
@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
var resultValue = new ContentFieldData(); 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; resultValue[partitionValue.Key] = partitionValue.Value;
} }

22
src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs

@ -7,21 +7,24 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Contents namespace Squidex.Domain.Apps.Core.Contents
{ {
public sealed class ContentFieldData : Dictionary<string, JToken>, IEquatable<ContentFieldData> public sealed class ContentFieldData : Dictionary<string, IJsonValue>, IEquatable<ContentFieldData>
{ {
private static readonly JTokenEqualityComparer JTokenEqualityComparer = new JTokenEqualityComparer();
public ContentFieldData() public ContentFieldData()
: base(StringComparer.OrdinalIgnoreCase) : 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)); Guard.NotNullOrEmpty(key, nameof(key));
@ -30,11 +33,6 @@ namespace Squidex.Domain.Apps.Core.Contents
return this; return this;
} }
public ContentFieldData AddValue(JToken value)
{
return AddValue(InvariantPartitioning.Instance.Master.Key, value);
}
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return Equals(obj as ContentFieldData); return Equals(obj as ContentFieldData);
@ -42,12 +40,12 @@ namespace Squidex.Domain.Apps.Core.Contents
public bool Equals(ContentFieldData other) public bool Equals(ContentFieldData other)
{ {
return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other, EqualityComparer<string>.Default, JTokenEqualityComparer)); return other != null && (ReferenceEquals(this, other) || this.EqualsDictionary(other));
} }
public override int GetHashCode() public override int GetHashCode()
{ {
return this.DictionaryHashCode(EqualityComparer<string>.Default, JTokenEqualityComparer); return this.DictionaryHashCode();
} }
} }
} }

68
src/Squidex.Domain.Apps.Core.Model/DictionaryWrapper{TKey,TValue}.cs

@ -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<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
{
private readonly ImmutableDictionary<TKey, TValue> inner;
public TValue this[TKey key]
{
get { return inner[key]; }
}
public IEnumerable<TKey> Keys
{
get { return inner.Keys; }
}
public IEnumerable<TValue> Values
{
get { return inner.Values; }
}
public int Count
{
get { return inner.Count; }
}
protected ImmutableDictionary<TKey, TValue> Inner
{
get { return inner; }
}
protected DictionaryWrapper(ImmutableDictionary<TKey, TValue> 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<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return inner.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return inner.GetEnumerator();
}
}
}

2
src/Squidex.Domain.Apps.Core.Model/Rules/Json/RuleConverter.cs

@ -7,7 +7,7 @@
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Rules.Json namespace Squidex.Domain.Apps.Core.Rules.Json
{ {

5
src/Squidex.Domain.Apps.Core.Model/Rules/RuleJob.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
namespace Squidex.Domain.Apps.Core.Rules namespace Squidex.Domain.Apps.Core.Rules
@ -23,12 +22,12 @@ namespace Squidex.Domain.Apps.Core.Rules
public string ActionName { get; set; } public string ActionName { get; set; }
public string ActionData { get; set; }
public string Description { get; set; } public string Description { get; set; }
public Instant Created { get; set; } public Instant Created { get; set; }
public Instant Expires { get; set; } public Instant Expires { get; set; }
public JObject ActionData { get; set; }
} }
} }

4
src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/ContentChangedTrigger.cs

@ -5,15 +5,15 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Rules.Triggers namespace Squidex.Domain.Apps.Core.Rules.Triggers
{ {
[TypeName(nameof(ContentChangedTrigger))] [TypeName(nameof(ContentChangedTrigger))]
public sealed class ContentChangedTrigger : RuleTrigger public sealed class ContentChangedTrigger : RuleTrigger
{ {
public ImmutableList<ContentChangedTriggerSchema> Schemas { get; set; } public ReadOnlyCollection<ContentChangedTriggerSchema> Schemas { get; set; }
public bool HandleAll { get; set; } public bool HandleAll { get; set; }

11
src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayField.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
@ -30,11 +31,19 @@ namespace Squidex.Domain.Apps.Core.Schemas
get { return fields.ByName; } 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) : 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<NestedField>(fields);
}
[Pure] [Pure]
public ArrayField DeleteField(long fieldId) public ArrayField DeleteField(long fieldId)
{ {

6
src/Squidex.Domain.Apps.Core.Model/Schemas/ArrayFieldProperties.cs

@ -27,12 +27,12 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IArrayField)field); 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(); throw new NotSupportedException();
} }

12
src/Squidex.Domain.Apps.Core.Model/Schemas/AssetsFieldProperties.cs

@ -5,8 +5,8 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
public int? AspectHeight { get; set; } public int? AspectHeight { get; set; }
public ImmutableList<string> AllowedExtensions { get; set; } public ReadOnlyCollection<string> AllowedExtensions { get; set; }
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{ {
@ -47,14 +47,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IField<AssetsFieldProperties>)field); return visitor.Visit((IField<AssetsFieldProperties>)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);
} }
} }
} }

8
src/Squidex.Domain.Apps.Core.Model/Schemas/BooleanFieldProperties.cs

@ -28,14 +28,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IField<BooleanFieldProperties>)field); return visitor.Visit((IField<BooleanFieldProperties>)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);
} }
} }
} }

8
src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs

@ -33,14 +33,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IField<DateTimeFieldProperties>)field); return visitor.Visit((IField<DateTimeFieldProperties>)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);
} }
} }
} }

29
src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -17,10 +16,13 @@ namespace Squidex.Domain.Apps.Core.Schemas
public sealed class FieldCollection<T> : Cloneable<FieldCollection<T>> where T : IField public sealed class FieldCollection<T> : Cloneable<FieldCollection<T>> where T : IField
{ {
public static readonly FieldCollection<T> Empty = new FieldCollection<T>(); public static readonly FieldCollection<T> Empty = new FieldCollection<T>();
private static readonly Dictionary<long, T> EmptyById = new Dictionary<long, T>();
private static readonly Dictionary<string, T> EmptyByString = new Dictionary<string, T>();
private ImmutableArray<T> fieldsOrdered = ImmutableArray<T>.Empty; private T[] fieldsOrdered;
private ImmutableDictionary<long, T> fieldsById; private Dictionary<long, T> fieldsById;
private ImmutableDictionary<string, T> fieldsByName; private Dictionary<string, T> fieldsByName;
public IReadOnlyList<T> Ordered public IReadOnlyList<T> Ordered
{ {
@ -35,11 +37,11 @@ namespace Squidex.Domain.Apps.Core.Schemas
{ {
if (fieldsOrdered.Length == 0) if (fieldsOrdered.Length == 0)
{ {
fieldsById = ImmutableDictionary<long, T>.Empty; fieldsById = EmptyById;
} }
else 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) if (fieldsOrdered.Length == 0)
{ {
fieldsByName = ImmutableDictionary<string, T>.Empty; fieldsByName = EmptyByString;
} }
else 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() private FieldCollection()
{ {
fieldsOrdered = Array.Empty<T>();
} }
public FieldCollection(T[] fields) public FieldCollection(T[] fields)
{ {
Guard.NotNull(fields, nameof(fields)); Guard.NotNull(fields, nameof(fields));
fieldsOrdered = ImmutableArray.Create(fields); fieldsOrdered = fields;
} }
protected override void OnCloned() protected override void OnCloned()
@ -94,7 +97,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
return Clone(clone => 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 => 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 => 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 => return Clone(clone =>
{ {
clone.fieldsOrdered = clone.fieldsOrdered.Replace(field, typedField); clone.fieldsOrdered = clone.fieldsOrdered.Select(x => ReferenceEquals(x, field) ? newField : x).ToArray();
}); });
} }
} }

4
src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs

@ -21,8 +21,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
public abstract T Accept<T>(IFieldVisitor<T> visitor, IField field); public abstract T Accept<T>(IFieldVisitor<T> 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);
} }
} }

8
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); 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); CheckProperties(properties);
return properties.CreateNestedField(id, name); return properties.CreateNestedField(id, name, settings);
} }
private void CheckProperties(FieldProperties properties) private void CheckProperties(FieldProperties properties)

164
src/Squidex.Domain.Apps.Core.Model/Schemas/Fields.cs

@ -13,117 +13,107 @@ namespace Squidex.Domain.Apps.Core.Schemas
{ {
public static RootField<ArrayFieldProperties> Array(long id, string name, Partitioning partitioning, params NestedField[] fields) public static RootField<ArrayFieldProperties> Array(long id, string name, Partitioning partitioning, params NestedField[] fields)
{ {
var result = new ArrayField(id, name, partitioning, new ArrayFieldProperties()); return new ArrayField(id, name, partitioning, fields: fields);
if (fields != null)
{
foreach (var field in fields)
{
result = result.AddField(field);
}
}
return result;
} }
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<AssetsFieldProperties> Assets(long id, string name, Partitioning partitioning, AssetsFieldProperties properties = null) public static RootField<AssetsFieldProperties> Assets(long id, string name, Partitioning partitioning, AssetsFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new RootField<AssetsFieldProperties>(id, name, partitioning, properties ?? new AssetsFieldProperties()); return new RootField<AssetsFieldProperties>(id, name, partitioning, properties, settings);
} }
public static RootField<BooleanFieldProperties> Boolean(long id, string name, Partitioning partitioning, BooleanFieldProperties properties = null) public static RootField<BooleanFieldProperties> Boolean(long id, string name, Partitioning partitioning, BooleanFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new RootField<BooleanFieldProperties>(id, name, partitioning, properties ?? new BooleanFieldProperties()); return new RootField<BooleanFieldProperties>(id, name, partitioning, properties, settings);
} }
public static RootField<DateTimeFieldProperties> DateTime(long id, string name, Partitioning partitioning, DateTimeFieldProperties properties = null) public static RootField<DateTimeFieldProperties> DateTime(long id, string name, Partitioning partitioning, DateTimeFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new RootField<DateTimeFieldProperties>(id, name, partitioning, properties ?? new DateTimeFieldProperties()); return new RootField<DateTimeFieldProperties>(id, name, partitioning, properties, settings);
} }
public static RootField<GeolocationFieldProperties> Geolocation(long id, string name, Partitioning partitioning, GeolocationFieldProperties properties = null) public static RootField<GeolocationFieldProperties> Geolocation(long id, string name, Partitioning partitioning, GeolocationFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new RootField<GeolocationFieldProperties>(id, name, partitioning, properties ?? new GeolocationFieldProperties()); return new RootField<GeolocationFieldProperties>(id, name, partitioning, properties, settings);
} }
public static RootField<JsonFieldProperties> Json(long id, string name, Partitioning partitioning, JsonFieldProperties properties = null) public static RootField<JsonFieldProperties> Json(long id, string name, Partitioning partitioning, JsonFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new RootField<JsonFieldProperties>(id, name, partitioning, properties ?? new JsonFieldProperties()); return new RootField<JsonFieldProperties>(id, name, partitioning, properties, settings);
} }
public static RootField<NumberFieldProperties> Number(long id, string name, Partitioning partitioning, NumberFieldProperties properties = null) public static RootField<NumberFieldProperties> Number(long id, string name, Partitioning partitioning, NumberFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new RootField<NumberFieldProperties>(id, name, partitioning, properties ?? new NumberFieldProperties()); return new RootField<NumberFieldProperties>(id, name, partitioning, properties, settings);
} }
public static RootField<ReferencesFieldProperties> References(long id, string name, Partitioning partitioning, ReferencesFieldProperties properties = null) public static RootField<ReferencesFieldProperties> References(long id, string name, Partitioning partitioning, ReferencesFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new RootField<ReferencesFieldProperties>(id, name, partitioning, properties ?? new ReferencesFieldProperties()); return new RootField<ReferencesFieldProperties>(id, name, partitioning, properties, settings);
} }
public static RootField<StringFieldProperties> String(long id, string name, Partitioning partitioning, StringFieldProperties properties = null) public static RootField<StringFieldProperties> String(long id, string name, Partitioning partitioning, StringFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new RootField<StringFieldProperties>(id, name, partitioning, properties ?? new StringFieldProperties()); return new RootField<StringFieldProperties>(id, name, partitioning, properties, settings);
} }
public static RootField<TagsFieldProperties> Tags(long id, string name, Partitioning partitioning, TagsFieldProperties properties = null) public static RootField<TagsFieldProperties> Tags(long id, string name, Partitioning partitioning, TagsFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new RootField<TagsFieldProperties>(id, name, partitioning, properties ?? new TagsFieldProperties()); return new RootField<TagsFieldProperties>(id, name, partitioning, properties, settings);
} }
public static NestedField<AssetsFieldProperties> Assets(long id, string name, AssetsFieldProperties properties = null) public static NestedField<AssetsFieldProperties> Assets(long id, string name, AssetsFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new NestedField<AssetsFieldProperties>(id, name, properties ?? new AssetsFieldProperties()); return new NestedField<AssetsFieldProperties>(id, name, properties, settings);
} }
public static NestedField<BooleanFieldProperties> Boolean(long id, string name, BooleanFieldProperties properties = null) public static NestedField<BooleanFieldProperties> Boolean(long id, string name, BooleanFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new NestedField<BooleanFieldProperties>(id, name, properties ?? new BooleanFieldProperties()); return new NestedField<BooleanFieldProperties>(id, name, properties, settings);
} }
public static NestedField<DateTimeFieldProperties> DateTime(long id, string name, DateTimeFieldProperties properties = null) public static NestedField<DateTimeFieldProperties> DateTime(long id, string name, DateTimeFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new NestedField<DateTimeFieldProperties>(id, name, properties ?? new DateTimeFieldProperties()); return new NestedField<DateTimeFieldProperties>(id, name, properties, settings);
} }
public static NestedField<GeolocationFieldProperties> Geolocation(long id, string name, GeolocationFieldProperties properties = null) public static NestedField<GeolocationFieldProperties> Geolocation(long id, string name, GeolocationFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new NestedField<GeolocationFieldProperties>(id, name, properties ?? new GeolocationFieldProperties()); return new NestedField<GeolocationFieldProperties>(id, name, properties, settings);
} }
public static NestedField<JsonFieldProperties> Json(long id, string name, JsonFieldProperties properties = null) public static NestedField<JsonFieldProperties> Json(long id, string name, JsonFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new NestedField<JsonFieldProperties>(id, name, properties ?? new JsonFieldProperties()); return new NestedField<JsonFieldProperties>(id, name, properties, settings);
} }
public static NestedField<NumberFieldProperties> Number(long id, string name, NumberFieldProperties properties = null) public static NestedField<NumberFieldProperties> Number(long id, string name, NumberFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new NestedField<NumberFieldProperties>(id, name, properties ?? new NumberFieldProperties()); return new NestedField<NumberFieldProperties>(id, name, properties, settings);
} }
public static NestedField<ReferencesFieldProperties> References(long id, string name, ReferencesFieldProperties properties = null) public static NestedField<ReferencesFieldProperties> References(long id, string name, ReferencesFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new NestedField<ReferencesFieldProperties>(id, name, properties ?? new ReferencesFieldProperties()); return new NestedField<ReferencesFieldProperties>(id, name, properties, settings);
} }
public static NestedField<StringFieldProperties> String(long id, string name, StringFieldProperties properties = null) public static NestedField<StringFieldProperties> String(long id, string name, StringFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new NestedField<StringFieldProperties>(id, name, properties ?? new StringFieldProperties()); return new NestedField<StringFieldProperties>(id, name, properties, settings);
} }
public static NestedField<TagsFieldProperties> Tags(long id, string name, TagsFieldProperties properties = null) public static NestedField<TagsFieldProperties> Tags(long id, string name, TagsFieldProperties properties = null, IFieldSettings settings = null)
{ {
return new NestedField<TagsFieldProperties>(id, name, properties ?? new TagsFieldProperties()); return new NestedField<TagsFieldProperties>(id, name, properties, settings);
} }
public static Schema AddArray(this Schema schema, long id, string name, Partitioning partitioning, Func<ArrayField, ArrayField> handler, ArrayFieldProperties properties = null) public static Schema AddArray(this Schema schema, long id, string name, Partitioning partitioning, Func<ArrayField, ArrayField> 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) if (handler != null)
{ {
@ -133,94 +123,94 @@ namespace Squidex.Domain.Apps.Core.Schemas
return schema.AddField(field); 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));
} }
} }
} }

8
src/Squidex.Domain.Apps.Core.Model/Schemas/GeolocationFieldProperties.cs

@ -24,14 +24,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IField<GeolocationFieldProperties>)field); return visitor.Visit((IField<GeolocationFieldProperties>)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);
} }
} }
} }

8
src/Squidex.Domain.Apps.Core.Model/Schemas/IField.cs

@ -7,18 +7,12 @@
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
public interface IField public interface IField : IFieldSettings
{ {
long Id { get; } long Id { get; }
string Name { get; } string Name { get; }
bool IsLocked { get; }
bool IsDisabled { get; }
bool IsHidden { get; }
FieldProperties RawProperties { get; } FieldProperties RawProperties { get; }
T Accept<T>(IFieldVisitor<T> visitor); T Accept<T>(IFieldVisitor<T> visitor);

18
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; }
}
}

25
src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonFieldModel.cs

@ -5,12 +5,15 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using Newtonsoft.Json; 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 namespace Squidex.Domain.Apps.Core.Schemas.Json
{ {
public sealed class JsonFieldModel public sealed class JsonFieldModel : IFieldSettings
{ {
[JsonProperty] [JsonProperty]
public long Id { get; set; } public long Id { get; set; }
@ -34,6 +37,22 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
public FieldProperties Properties { get; set; } public FieldProperties Properties { get; set; }
[JsonProperty] [JsonProperty]
public List<JsonNestedFieldModel> 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<NestedField>();
return new ArrayField(Id, Name, partitioning, nested, arrayProperties, this);
}
else
{
return Properties.CreateRootField(Id, Name, partitioning, this);
}
}
} }
} }

12
src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonNestedFieldModel.cs

@ -9,7 +9,7 @@ using Newtonsoft.Json;
namespace Squidex.Domain.Apps.Core.Schemas.Json namespace Squidex.Domain.Apps.Core.Schemas.Json
{ {
public sealed class JsonNestedFieldModel public sealed class JsonNestedFieldModel : IFieldSettings
{ {
[JsonProperty] [JsonProperty]
public long Id { get; set; } public long Id { get; set; }
@ -25,5 +25,15 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
[JsonProperty] [JsonProperty]
public FieldProperties Properties { get; set; } public FieldProperties Properties { get; set; }
public bool IsLocked
{
get { return false; }
}
public NestedField ToNestedField()
{
return Properties.CreateNestedField(Id, Name, this);
}
} }
} }

74
src/Squidex.Domain.Apps.Core.Model/Schemas/Json/JsonSchemaModel.cs

@ -5,16 +5,15 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas.Json namespace Squidex.Domain.Apps.Core.Schemas.Json
{ {
public sealed class JsonSchemaModel public sealed class JsonSchemaModel
{ {
private static readonly RootField[] Empty = new RootField[0];
[JsonProperty] [JsonProperty]
public string Name { get; set; } public string Name { get; set; }
@ -25,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
public SchemaProperties Properties { get; set; } public SchemaProperties Properties { get; set; }
[JsonProperty] [JsonProperty]
public List<JsonFieldModel> Fields { get; set; } public JsonFieldModel[] Fields { get; set; }
public JsonSchemaModel() public JsonSchemaModel()
{ {
@ -38,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
Properties = schema.Properties; Properties = schema.Properties;
Fields = Fields =
schema.Fields.Select(x => schema.Fields.ToArray(x =>
new JsonFieldModel new JsonFieldModel
{ {
Id = x.Id, Id = x.Id,
@ -49,16 +48,16 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
IsDisabled = x.IsDisabled, IsDisabled = x.IsDisabled,
Partitioning = x.Partitioning.Key, Partitioning = x.Partitioning.Key,
Properties = x.RawProperties Properties = x.RawProperties
}).ToList(); });
IsPublished = schema.IsPublished; IsPublished = schema.IsPublished;
} }
private static List<JsonNestedFieldModel> CreateChildren(IField field) private static JsonNestedFieldModel[] CreateChildren(IField field)
{ {
if (field is ArrayField arrayField) if (field is ArrayField arrayField)
{ {
return arrayField.Fields.Select(x => return arrayField.Fields.ToArray(x =>
new JsonNestedFieldModel new JsonNestedFieldModel
{ {
Id = x.Id, Id = x.Id,
@ -66,68 +65,15 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
IsHidden = x.IsHidden, IsHidden = x.IsHidden,
IsDisabled = x.IsDisabled, IsDisabled = x.IsDisabled,
Properties = x.RawProperties Properties = x.RawProperties
}).ToList(); });
} }
return null; return null;
} }
public Schema ToSchema(FieldRegistry registry) public Schema ToSchema()
{ {
var fields = Empty; var fields = Fields.ToArray(f => f.ToField()) ?? Array.Empty<RootField>();
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;
}
}
return new Schema(Name, fields, Properties, IsPublished); return new Schema(Name, fields, Properties, IsPublished);
} }

14
src/Squidex.Domain.Apps.Core.Model/Schemas/Json/SchemaConverter.cs

@ -7,22 +7,12 @@
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.Schemas.Json namespace Squidex.Domain.Apps.Core.Schemas.Json
{ {
public sealed class SchemaConverter : JsonClassConverter<Schema> public sealed class SchemaConverter : JsonClassConverter<Schema>
{ {
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) protected override void WriteValue(JsonWriter writer, Schema value, JsonSerializer serializer)
{ {
serializer.Serialize(writer, new JsonSchemaModel(value)); 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) protected override Schema ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
return serializer.Deserialize<JsonSchemaModel>(reader).ToSchema(fieldRegistry); return serializer.Deserialize<JsonSchemaModel>(reader).ToSchema();
} }
} }
} }

8
src/Squidex.Domain.Apps.Core.Model/Schemas/JsonFieldProperties.cs

@ -22,14 +22,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IField<JsonFieldProperties>)field); return visitor.Visit((IField<JsonFieldProperties>)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);
} }
} }
} }

9
src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField.cs

@ -45,13 +45,20 @@ namespace Squidex.Domain.Apps.Core.Schemas
public abstract FieldProperties RawProperties { get; } 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.NotNullOrEmpty(name, nameof(name));
Guard.GreaterThan(id, 0, nameof(id)); Guard.GreaterThan(id, 0, nameof(id));
fieldId = id; fieldId = id;
fieldName = name; fieldName = name;
if (settings != null)
{
isLocked = settings.IsLocked;
isHidden = settings.IsHidden;
isDisabled = settings.IsDisabled;
}
} }
[Pure] [Pure]

8
src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs

@ -25,12 +25,10 @@ namespace Squidex.Domain.Apps.Core.Schemas
get { return properties; } get { return properties; }
} }
public NestedField(long id, string name, T properties) public NestedField(long id, string name, T properties = null, IFieldSettings settings = null)
: base(id, name) : base(id, name, settings)
{ {
Guard.NotNull(properties, nameof(properties)); SetProperties(properties ?? new T());
SetProperties(properties);
} }
[Pure] [Pure]

14
src/Squidex.Domain.Apps.Core.Model/Schemas/NumberFieldProperties.cs

@ -5,15 +5,15 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
[TypeName("NumberField")] [TypeName("NumberField")]
public sealed class NumberFieldProperties : FieldProperties public sealed class NumberFieldProperties : FieldProperties
{ {
public ImmutableList<double> AllowedValues { get; set; } public ReadOnlyCollection<double> AllowedValues { get; set; }
public double? MaxValue { get; set; } public double? MaxValue { get; set; }
@ -21,6 +21,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
public double? DefaultValue { get; set; } public double? DefaultValue { get; set; }
public bool IsUnique { get; set; }
public bool InlineEditable { get; set; } public bool InlineEditable { get; set; }
public NumberFieldEditor Editor { get; set; } public NumberFieldEditor Editor { get; set; }
@ -35,14 +37,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IField<NumberFieldProperties>)field); return visitor.Visit((IField<NumberFieldProperties>)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);
} }
} }
} }

8
src/Squidex.Domain.Apps.Core.Model/Schemas/ReferencesFieldProperties.cs

@ -29,14 +29,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IField<ReferencesFieldProperties>)field); return visitor.Visit((IField<ReferencesFieldProperties>)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);
} }
} }
} }

9
src/Squidex.Domain.Apps.Core.Model/Schemas/RootField.cs

@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
public abstract FieldProperties RawProperties { get; } 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.NotNullOrEmpty(name, nameof(name));
Guard.GreaterThan(id, 0, nameof(id)); Guard.GreaterThan(id, 0, nameof(id));
@ -61,6 +61,13 @@ namespace Squidex.Domain.Apps.Core.Schemas
fieldName = name; fieldName = name;
this.partitioning = partitioning; this.partitioning = partitioning;
if (settings != null)
{
isLocked = settings.IsLocked;
isHidden = settings.IsHidden;
isDisabled = settings.IsDisabled;
}
} }
[Pure] [Pure]

8
src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs

@ -25,12 +25,10 @@ namespace Squidex.Domain.Apps.Core.Schemas
get { return properties; } get { return properties; }
} }
public RootField(long id, string name, Partitioning partitioning, T properties) public RootField(long id, string name, Partitioning partitioning, T properties = null, IFieldSettings settings = null)
: base(id, name, partitioning) : base(id, name, partitioning, settings)
{ {
Guard.NotNull(properties, nameof(properties)); SetProperties(properties ?? new T());
SetProperties(properties);
} }
[Pure] [Pure]

14
src/Squidex.Domain.Apps.Core.Model/Schemas/StringFieldProperties.cs

@ -5,20 +5,22 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
[TypeName("StringField")] [TypeName("StringField")]
public sealed class StringFieldProperties : FieldProperties public sealed class StringFieldProperties : FieldProperties
{ {
public ImmutableList<string> AllowedValues { get; set; } public ReadOnlyCollection<string> AllowedValues { get; set; }
public int? MinLength { get; set; } public int? MinLength { get; set; }
public int? MaxLength { get; set; } public int? MaxLength { get; set; }
public bool IsUnique { get; set; }
public bool InlineEditable { get; set; } public bool InlineEditable { get; set; }
public string DefaultValue { get; set; } public string DefaultValue { get; set; }
@ -39,14 +41,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IField<StringFieldProperties>)field); return visitor.Visit((IField<StringFieldProperties>)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);
} }
} }
} }

16
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
}
}

13
src/Squidex.Domain.Apps.Core.Model/Schemas/TagsFieldProperties.cs

@ -6,16 +6,21 @@
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure; using Squidex.Infrastructure;
using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas namespace Squidex.Domain.Apps.Core.Schemas
{ {
[TypeName("TagsField")] [TypeName("TagsField")]
public sealed class TagsFieldProperties : FieldProperties public sealed class TagsFieldProperties : FieldProperties
{ {
public ReadOnlyCollection<string> AllowedValues { get; set; }
public int? MinItems { get; set; } public int? MinItems { get; set; }
public int? MaxItems { get; set; } public int? MaxItems { get; set; }
public TagsFieldEditor Editor { get; set; }
public TagsFieldNormalization Normalization { get; set; } public TagsFieldNormalization Normalization { get; set; }
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor) public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
@ -28,14 +33,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IField<TagsFieldProperties>)field); return visitor.Visit((IField<TagsFieldProperties>)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);
} }
} }
} }

19
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs

@ -7,13 +7,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
@ -41,11 +40,11 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return result; 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) if (text.Length <= maxFieldLength)
{ {
@ -57,18 +56,18 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
stringBuilder.Append(text); 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); 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);
} }
} }
} }

6
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs

@ -7,10 +7,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
languagePreferences = languagePreferences.Union(languageConfig.LanguageFallbacks).ToList(); languagePreferences = languagePreferences.Union(languageConfig.LanguageFallbacks).ToList();
} }
var result = new Dictionary<string, JToken>(); var result = new Dictionary<string, IJsonValue>();
foreach (var fieldValue in content) foreach (var fieldValue in content)
{ {
@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
foreach (var language in languagePreferences) 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; result[fieldValue.Key] = value;

17
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs

@ -8,13 +8,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
#pragma warning disable RECS0002 // Convert anonymous method to method group #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) foreach (var value in data.Values)
{ {
if (value.IsNull()) if (value.Type == JsonValueType.Null)
{ {
continue; continue;
} }
@ -78,13 +77,13 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
foreach (var partition in data) 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++) for (var i = 0; i < array.Count; i++)
{ {
var id = array[i].ToString(); 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) foreach (var partition in data)
{ {
if (!(partition.Value is JArray jArray)) if (!(partition.Value is JsonArray array))
{ {
continue; continue;
} }
var newArray = new JArray(); var newArray = JsonValue.Array();
foreach (var item in jArray.OfType<JObject>()) foreach (var item in array.OfType<JsonObject>())
{ {
var newItem = new JObject(); var newItem = JsonValue.Object();
foreach (var kvp in item) foreach (var kvp in item)
{ {

4
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/Value.cs

@ -5,12 +5,12 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Newtonsoft.Json.Linq; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
public static class Value public static class Value
{ {
public static readonly JToken Unset = JValue.CreateUndefined(); public static readonly IJsonValue Unset = JsonValue.Create("UNSET");
} }
} }

22
src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs

@ -7,41 +7,41 @@
using System; using System;
using System.Text; using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent 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 class ValueConverters
{ {
public static ValueConverter DecodeJson() public static ValueConverter DecodeJson(IJsonSerializer jsonSerializer)
{ {
return (value, field) => return (value, field) =>
{ {
if (!value.IsNull() && field is IField<JsonFieldProperties>) if (field is IField<JsonFieldProperties> && value is JsonScalar<string> 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<IJsonValue>(decoded);
} }
return value; return value;
}; };
} }
public static ValueConverter EncodeJson() public static ValueConverter EncodeJson(IJsonSerializer jsonSerializer)
{ {
return (value, field) => return (value, field) =>
{ {
if (!value.IsNull() && field is IField<JsonFieldProperties>) if (value.Type != JsonValueType.Null && field is IField<JsonFieldProperties>)
{ {
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; return value;
@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
return (value, field) => return (value, field) =>
{ {
if (value.IsNull()) if (value.Type == JsonValueType.Null)
{ {
return value; return value;
} }

11
src/Squidex.Domain.Apps.Core.Operations/EnrichContent/ContentEnricher.cs

@ -5,12 +5,11 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.EnrichContent namespace Squidex.Domain.Apps.Core.EnrichContent
{ {
@ -56,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant()); var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant());
if (field.RawProperties.IsRequired || defaultValue.IsNull()) if (field.RawProperties.IsRequired || defaultValue.Type == JsonValueType.Null)
{ {
return; return;
} }
@ -65,13 +64,13 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
if (!fieldData.TryGetValue(key, out var value) || ShouldApplyDefaultValue(field, value)) 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<StringFieldProperties> && value is JValue jValue && Equals(jValue.Value, string.Empty)); return value.Type == JsonValueType.Null || (field is IField<StringFieldProperties> && value is JsonScalar<string> s && string.IsNullOrEmpty(s.Value));
} }
} }
} }

50
src/Squidex.Domain.Apps.Core.Operations/EnrichContent/DefaultValueFactory.cs

@ -6,14 +6,14 @@
// ========================================================================== // ==========================================================================
using System.Globalization; using System.Globalization;
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.EnrichContent namespace Squidex.Domain.Apps.Core.EnrichContent
{ {
public sealed class DefaultValueFactory : IFieldVisitor<JToken> public sealed class DefaultValueFactory : IFieldVisitor<IJsonValue>
{ {
private readonly Instant now; private readonly Instant now;
@ -22,71 +22,71 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
this.now = now; this.now = now;
} }
public static JToken CreateDefaultValue(IField field, Instant now) public static IJsonValue CreateDefaultValue(IField field, Instant now)
{ {
Guard.NotNull(field, nameof(field)); Guard.NotNull(field, nameof(field));
return field.Accept(new DefaultValueFactory(now)); 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<AssetsFieldProperties> field) public IJsonValue Visit(IField<AssetsFieldProperties> field)
{ {
return new JArray(); return JsonValue.Array();
} }
public JToken Visit(IField<BooleanFieldProperties> field) public IJsonValue Visit(IField<BooleanFieldProperties> field)
{ {
return field.Properties.DefaultValue; return JsonValue.Create(field.Properties.DefaultValue);
} }
public JToken Visit(IField<GeolocationFieldProperties> field) public IJsonValue Visit(IField<GeolocationFieldProperties> field)
{ {
return JValue.CreateNull(); return JsonValue.Null;
} }
public JToken Visit(IField<JsonFieldProperties> field) public IJsonValue Visit(IField<JsonFieldProperties> field)
{ {
return JValue.CreateNull(); return JsonValue.Object();
} }
public JToken Visit(IField<NumberFieldProperties> field) public IJsonValue Visit(IField<NumberFieldProperties> field)
{ {
return field.Properties.DefaultValue; return JsonValue.Create(field.Properties.DefaultValue);
} }
public JToken Visit(IField<ReferencesFieldProperties> field) public IJsonValue Visit(IField<ReferencesFieldProperties> field)
{ {
return new JArray(); return JsonValue.Array();
} }
public JToken Visit(IField<StringFieldProperties> field) public IJsonValue Visit(IField<StringFieldProperties> field)
{ {
return field.Properties.DefaultValue; return JsonValue.Create(field.Properties.DefaultValue);
} }
public JToken Visit(IField<TagsFieldProperties> field) public IJsonValue Visit(IField<TagsFieldProperties> field)
{ {
return new JArray(); return JsonValue.Array();
} }
public JToken Visit(IField<DateTimeFieldProperties> field) public IJsonValue Visit(IField<DateTimeFieldProperties> field)
{ {
if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Now) if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Now)
{ {
return now.ToString(); return JsonValue.Create(now.ToString());
} }
if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Today) 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());
} }
} }
} }

4
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.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
continue; 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); var ids = field.ExtractReferences(partitionValue.Value);

36
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs

@ -7,44 +7,44 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public sealed class ReferencesCleaner : IFieldVisitor<JToken> public sealed class ReferencesCleaner : IFieldVisitor<IJsonValue>
{ {
private readonly JToken value; private readonly IJsonValue value;
private readonly ICollection<Guid> oldReferences; private readonly ICollection<Guid> oldReferences;
private ReferencesCleaner(JToken value, ICollection<Guid> oldReferences) private ReferencesCleaner(IJsonValue value, ICollection<Guid> oldReferences)
{ {
this.value = value; this.value = value;
this.oldReferences = oldReferences; this.oldReferences = oldReferences;
} }
public static JToken CleanReferences(IField field, JToken value, ICollection<Guid> oldReferences) public static IJsonValue CleanReferences(IField field, IJsonValue value, ICollection<Guid> oldReferences)
{ {
return field.Accept(new ReferencesCleaner(value, oldReferences)); return field.Accept(new ReferencesCleaner(value, oldReferences));
} }
public JToken Visit(IField<AssetsFieldProperties> field) public IJsonValue Visit(IField<AssetsFieldProperties> field)
{ {
return CleanIds(); return CleanIds();
} }
public JToken Visit(IField<ReferencesFieldProperties> field) public IJsonValue Visit(IField<ReferencesFieldProperties> field)
{ {
if (oldReferences.Contains(field.Properties.SchemaId)) if (oldReferences.Contains(field.Properties.SchemaId))
{ {
return new JArray(); return JsonValue.Array();
} }
return CleanIds(); return CleanIds();
} }
private JToken CleanIds() private IJsonValue CleanIds()
{ {
var ids = value.ToGuidSet(); var ids = value.ToGuidSet();
@ -55,45 +55,45 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
isRemoved |= ids.Remove(oldReference); isRemoved |= ids.Remove(oldReference);
} }
return isRemoved ? ids.ToJToken() : value; return isRemoved ? ids.ToJsonArray() : value;
} }
public JToken Visit(IField<BooleanFieldProperties> field) public IJsonValue Visit(IField<BooleanFieldProperties> field)
{ {
return value; return value;
} }
public JToken Visit(IField<DateTimeFieldProperties> field) public IJsonValue Visit(IField<DateTimeFieldProperties> field)
{ {
return value; return value;
} }
public JToken Visit(IField<GeolocationFieldProperties> field) public IJsonValue Visit(IField<GeolocationFieldProperties> field)
{ {
return value; return value;
} }
public JToken Visit(IField<JsonFieldProperties> field) public IJsonValue Visit(IField<JsonFieldProperties> field)
{ {
return value; return value;
} }
public JToken Visit(IField<NumberFieldProperties> field) public IJsonValue Visit(IField<NumberFieldProperties> field)
{ {
return value; return value;
} }
public JToken Visit(IField<StringFieldProperties> field) public IJsonValue Visit(IField<StringFieldProperties> field)
{ {
return value; return value;
} }
public JToken Visit(IField<TagsFieldProperties> field) public IJsonValue Visit(IField<TagsFieldProperties> field)
{ {
return value; return value;
} }
public JToken Visit(IArrayField field) public IJsonValue Visit(IArrayField field)
{ {
return value; return value;
} }

27
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs

@ -7,22 +7,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public static class ReferencesExtensions public static class ReferencesExtensions
{ {
public static IEnumerable<Guid> ExtractReferences(this IField field, JToken value) public static IEnumerable<Guid> ExtractReferences(this IField field, IJsonValue value)
{ {
return ReferencesExtractor.ExtractReferences(field, value); return ReferencesExtractor.ExtractReferences(field, value);
} }
public static JToken CleanReferences(this IField field, JToken value, ICollection<Guid> oldReferences) public static IJsonValue CleanReferences(this IField field, IJsonValue value, ICollection<Guid> oldReferences)
{ {
if (value.IsNull()) if (value.Type == JsonValueType.Null)
{ {
return value; return value;
} }
@ -30,31 +29,27 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
return ReferencesCleaner.CleanReferences(field, value, oldReferences); return ReferencesCleaner.CleanReferences(field, value, oldReferences);
} }
public static JToken ToJToken(this HashSet<Guid> ids) public static JsonArray ToJsonArray(this HashSet<Guid> ids)
{ {
var result = new JArray(); var result = JsonValue.Array();
foreach (var id in ids) foreach (var id in ids)
{ {
result.Add(new JValue(id)); result.Add(JsonValue.Create(id.ToString()));
} }
return result; return result;
} }
public static HashSet<Guid> ToGuidSet(this JToken value) public static HashSet<Guid> ToGuidSet(this IJsonValue value)
{ {
if (value is JArray ids) if (value is JsonArray array)
{ {
var result = new HashSet<Guid>(); var result = new HashSet<Guid>();
foreach (var id in ids) foreach (var id in array)
{ {
if (id.Type == JTokenType.Guid) if (id.Type == JsonValueType.String && Guid.TryParse(id.ToString(), out var guid))
{
result.Add((Guid)id);
}
else if (id.Type == JTokenType.String && Guid.TryParse((string)id, out var guid))
{ {
result.Add(guid); result.Add(guid);
} }

12
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs

@ -8,21 +8,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public sealed class ReferencesExtractor : IFieldVisitor<IEnumerable<Guid>> public sealed class ReferencesExtractor : IFieldVisitor<IEnumerable<Guid>>
{ {
private readonly JToken value; private readonly IJsonValue value;
private ReferencesExtractor(JToken value) private ReferencesExtractor(IJsonValue value)
{ {
this.value = value; this.value = value;
} }
public static IEnumerable<Guid> ExtractReferences(IField field, JToken value) public static IEnumerable<Guid> ExtractReferences(IField field, IJsonValue value)
{ {
return field.Accept(new ReferencesExtractor(value)); return field.Accept(new ReferencesExtractor(value));
} }
@ -31,9 +31,9 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
var result = new List<Guid>(); var result = new List<Guid>();
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) foreach (var nestedField in field.Fields)
{ {

4
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ValueReferencesConverter.cs

@ -8,7 +8,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Squidex.Domain.Apps.Core.ConvertContent; using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
return (value, field) => return (value, field) =>
{ {
if (value.IsNull()) if (value.Type == JsonValueType.Null)
{ {
return value; return value;
} }

6
src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedEvent.cs

@ -6,7 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using Newtonsoft.Json; using System.Runtime.Serialization;
using NodaTime; using NodaTime;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Shared.Users; using Squidex.Shared.Users;
@ -25,10 +25,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
public long Version { get; set; } public long Version { get; set; }
[JsonIgnore] [IgnoreDataMember]
public abstract Guid AggregateId { get; } public abstract Guid AggregateId { get; }
[JsonIgnore] [IgnoreDataMember]
public IUser User { get; set; } public IUser User { get; set; }
} }
} }

7
src/Squidex.Domain.Apps.Core.Operations/HandleRules/IRuleActionHandler.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
@ -17,8 +16,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{ {
Type ActionType { get; } 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);
} }
} }

28
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -25,29 +24,24 @@ namespace Squidex.Domain.Apps.Core.HandleRules
get { return typeof(TAction); } get { return typeof(TAction); }
} }
protected RuleActionHandler(RuleEventFormatter formatter) Type IRuleActionHandler.DataType
{ {
Guard.NotNull(formatter, nameof(formatter)); get { return typeof(TData); }
this.formatter = formatter;
} }
protected virtual string ToPayloadJson<T>(T @event) protected RuleActionHandler(RuleEventFormatter formatter)
{ {
return formatter.ToPayload(@event).ToString(); Guard.NotNull(formatter, nameof(formatter));
}
protected virtual string ToEnvelopeJson(EnrichedEvent @event) this.formatter = formatter;
{
return formatter.ToEnvelope(@event).ToString();
} }
protected virtual JObject ToPayload<T>(T @event) protected virtual string ToJson<T>(T @event)
{ {
return formatter.ToPayload(@event); return formatter.ToPayload(@event);
} }
protected virtual JObject ToEnvelope(EnrichedEvent @event) protected virtual string ToEnvelopeJson(EnrichedEvent @event)
{ {
return formatter.ToEnvelope(@event); return formatter.ToEnvelope(@event);
} }
@ -62,16 +56,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return formatter.Format(text, @event); 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); 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<TData>(); var typedData = (TData)data;
return await ExecuteJobAsync(typedData); return await ExecuteJobAsync(typedData);
} }

38
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -10,11 +10,11 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Shared.Users; using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.HandleRules 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 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 static readonly Regex ContentDataPlaceholderNew = new Regex(@"^\{CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}\}", RegexOptions.Compiled);
private readonly List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)> patterns = new List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)>(); private readonly List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)> patterns = new List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)>();
private readonly JsonSerializer serializer; private readonly IJsonSerializer jsonSerializer;
private readonly IRuleUrlGenerator urlGenerator; 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)); Guard.NotNull(urlGenerator, nameof(urlGenerator));
this.serializer = serializer; this.jsonSerializer = jsonSerializer;
this.urlGenerator = urlGenerator; this.urlGenerator = urlGenerator;
AddPattern("APP_ID", AppId); AddPattern("APP_ID", AppId);
@ -55,17 +55,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules
patterns.Add((placeholder.ToCharArray(), generator)); patterns.Add((placeholder.ToCharArray(), generator));
} }
public virtual JObject ToPayload<T>(T @event) public virtual string ToPayload<T>(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( return jsonSerializer.Serialize(new { type = @event.Name, payload = @event, timestamp = @event.Timestamp });
new JProperty("type", @event.Name),
new JProperty("payload", ToPayload(@event)),
new JProperty("timestamp", @event.Timestamp.ToString()));
} }
public string Format(string text, EnrichedEvent @event) 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++) 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; 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 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; return Undefined;
} }
if (value is JValue jValue) return value.ToString() ?? Undefined;
{
return jValue.Value.ToString();
}
return value.ToString(Formatting.Indented) ?? Undefined;
} }
} }
} }

21
src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -11,12 +11,12 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Core.HandleRules namespace Squidex.Domain.Apps.Core.HandleRules
{ {
@ -26,15 +26,18 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private readonly Dictionary<Type, IRuleTriggerHandler> ruleTriggerHandlers; private readonly Dictionary<Type, IRuleTriggerHandler> ruleTriggerHandlers;
private readonly TypeNameRegistry typeNameRegistry; private readonly TypeNameRegistry typeNameRegistry;
private readonly IEventEnricher eventEnricher; private readonly IEventEnricher eventEnricher;
private readonly IJsonSerializer jsonSerializer;
private readonly IClock clock; private readonly IClock clock;
public RuleService( public RuleService(
IEnumerable<IRuleTriggerHandler> ruleTriggerHandlers, IEnumerable<IRuleTriggerHandler> ruleTriggerHandlers,
IEnumerable<IRuleActionHandler> ruleActionHandlers, IEnumerable<IRuleActionHandler> ruleActionHandlers,
IEventEnricher eventEnricher, IEventEnricher eventEnricher,
IJsonSerializer jsonSerializer,
IClock clock, IClock clock,
TypeNameRegistry typeNameRegistry) TypeNameRegistry typeNameRegistry)
{ {
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
Guard.NotNull(ruleTriggerHandlers, nameof(ruleTriggerHandlers)); Guard.NotNull(ruleTriggerHandlers, nameof(ruleTriggerHandlers));
Guard.NotNull(ruleActionHandlers, nameof(ruleActionHandlers)); Guard.NotNull(ruleActionHandlers, nameof(ruleActionHandlers));
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry)); Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry));
@ -48,6 +51,8 @@ namespace Squidex.Domain.Apps.Core.HandleRules
this.eventEnricher = eventEnricher; this.eventEnricher = eventEnricher;
this.jsonSerializer = jsonSerializer;
this.clock = clock; this.clock = clock;
} }
@ -88,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var now = clock.GetCurrentInstant(); var now = clock.GetCurrentInstant();
var eventTime = var eventTime =
@event.Headers.Contains(CommonHeaders.Timestamp) ? @event.Headers.ContainsKey(CommonHeaders.Timestamp) ?
@event.Headers.Timestamp() : @event.Headers.Timestamp() :
now; now;
@ -104,11 +109,13 @@ namespace Squidex.Domain.Apps.Core.HandleRules
var actionName = typeNameRegistry.GetName(actionType); var actionName = typeNameRegistry.GetName(actionType);
var actionData = await actionHandler.CreateJobAsync(enrichedEvent, rule.Action); var actionData = await actionHandler.CreateJobAsync(enrichedEvent, rule.Action);
var json = jsonSerializer.Serialize(actionData.Data);
var job = new RuleJob var job = new RuleJob
{ {
JobId = Guid.NewGuid(), JobId = Guid.NewGuid(),
ActionName = actionName, ActionName = actionName,
ActionData = actionData.Data, ActionData = json,
AggregateId = enrichedEvent.AggregateId, AggregateId = enrichedEvent.AggregateId,
AppId = appEvent.AppId.Id, AppId = appEvent.AppId.Id,
Created = now, Created = now,
@ -120,14 +127,18 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return job; 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 try
{ {
var actionType = typeNameRegistry.GetType(actionName); var actionType = typeNameRegistry.GetType(actionName);
var actionWatch = Stopwatch.StartNew(); var actionWatch = Stopwatch.StartNew();
var result = await ruleActionHandlers[actionType].ExecuteJobAsync(job); var actionHandler = ruleActionHandlers[actionType];
var deserialized = jsonSerializer.Deserialize<object>(job, actionHandler.DataType);
var result = await actionHandler.ExecuteJobAsync(deserialized);
actionWatch.Stop(); actionWatch.Stop();

8
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs

@ -7,14 +7,14 @@
using Jint.Native; using Jint.Native;
using Jint.Runtime.Descriptors; using Jint.Runtime.Descriptors;
using Newtonsoft.Json.Linq; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{ {
public sealed class ContentFieldProperty : PropertyDescriptor public sealed class ContentFieldProperty : PropertyDescriptor
{ {
private readonly ContentFieldObject contentField; private readonly ContentFieldObject contentField;
private JToken contentValue; private IJsonValue contentValue;
private JsValue value; private JsValue value;
private bool isChanged; 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)); } get { return contentValue ?? (contentValue = JsonMapper.Map(value)); }
} }
@ -48,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
get { return isChanged; } get { return isChanged; }
} }
public ContentFieldProperty(ContentFieldObject contentField, JToken contentValue = null) public ContentFieldProperty(ContentFieldObject contentField, IJsonValue contentValue = null)
: base(null, true, true, true) : base(null, true, true, true)
{ {
this.contentField = contentField; this.contentField = contentField;

94
src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs

@ -9,61 +9,52 @@ using System;
using Jint; using Jint;
using Jint.Native; using Jint.Native;
using Jint.Native.Object; using Jint.Native.Object;
using Newtonsoft.Json.Linq; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{ {
public static class JsonMapper public static class JsonMapper
{ {
public static JsValue Map(JToken value, Engine engine) public static JsValue Map(IJsonValue value, Engine engine)
{ {
if (value == null) if (value == null)
{ {
return JsValue.Null; return JsValue.Null;
} }
switch (value.Type) switch (value)
{ {
case JTokenType.Date: case JsonNull n:
case JTokenType.Guid:
case JTokenType.String:
case JTokenType.Uri:
case JTokenType.TimeSpan:
return new JsValue((string)value);
case JTokenType.Null:
return JsValue.Null; return JsValue.Null;
case JTokenType.Undefined: case JsonScalar<string> s:
return JsValue.Undefined; return new JsValue(s.Value);
case JTokenType.Integer: case JsonScalar<bool> b:
return new JsValue((long)value); return new JsValue(b.Value);
case JTokenType.Float: case JsonScalar<double> b:
return new JsValue((double)value); return new JsValue(b.Value);
case JTokenType.Boolean: case JsonObject obj:
return new JsValue((bool)value); return FromObject(obj, engine);
case JTokenType.Object: case JsonArray arr:
return FromObject(value, engine); return FromArray(arr, 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);
}
} }
throw new ArgumentException("Invalid json type.", nameof(value)); 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); var target = new ObjectInstance(engine);
foreach (var property in obj) foreach (var property in obj)
@ -74,69 +65,64 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
return target; return target;
} }
public static JToken Map(JsValue value) public static IJsonValue Map(JsValue value)
{ {
if (value == null || value.IsNull()) if (value == null || value.IsNull() || value.IsUndefined())
{
return JValue.CreateNull();
}
if (value.IsUndefined())
{ {
return JValue.CreateUndefined(); return JsonValue.Null;
} }
if (value.IsString()) if (value.IsString())
{ {
return new JValue(value.AsString()); return JsonValue.Create(value.AsString());
} }
if (value.IsBoolean()) if (value.IsBoolean())
{ {
return new JValue(value.AsBoolean()); return JsonValue.Create(value.AsBoolean());
} }
if (value.IsNumber()) if (value.IsNumber())
{ {
return new JValue(value.AsNumber()); return JsonValue.Create(value.AsNumber());
} }
if (value.IsDate()) if (value.IsDate())
{ {
return new JValue(value.AsDate().ToDateTime()); return JsonValue.Create(value.AsDate().ToString());
} }
if (value.IsRegExp()) if (value.IsRegExp())
{ {
return JValue.CreateString(value.AsRegExp().Value?.ToString()); return JsonValue.Create(value.AsRegExp().Value?.ToString());
} }
if (value.IsArray()) if (value.IsArray())
{ {
var arr = value.AsArray(); var arr = value.AsArray();
var target = new JArray(); var result = JsonValue.Array();
for (var i = 0; i < arr.GetLength(); i++) 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()) if (value.IsObject())
{ {
var obj = value.AsObject(); var obj = value.AsObject();
var target = new JObject(); var result = JsonValue.Object();
foreach (var kvp in obj.GetOwnProperties()) 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)); throw new ArgumentException("Invalid json type.", nameof(value));

1
src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -16,7 +16,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Jint" Version="2.11.58" /> <PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.OData.Core" Version="7.5.2" /> <PackageReference Include="Microsoft.OData.Core" Version="7.5.2" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NJsonSchema" Version="9.12.3" /> <PackageReference Include="NJsonSchema" Version="9.12.3" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" /> <PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" PrivateAssets="all" />

34
src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs

@ -8,10 +8,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Tags namespace Squidex.Domain.Apps.Core.Tags
{ {
@ -24,10 +24,10 @@ namespace Squidex.Domain.Apps.Core.Tags
Guard.NotNull(newData, nameof(newData)); Guard.NotNull(newData, nameof(newData));
var newValues = new HashSet<string>(); var newValues = new HashSet<string>();
var newArrays = new List<JArray>(); var newArrays = new List<JsonArray>();
var oldValues = new HashSet<string>(); var oldValues = new HashSet<string>();
var oldArrays = new List<JArray>(); var oldArrays = new List<JsonArray>();
GetValues(schema, newValues, newArrays, newData); GetValues(schema, newValues, newArrays, newData);
@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Tags
{ {
if (normalized.TryGetValue(array[i].ToString(), out var result)) 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)); Guard.NotNull(schema, nameof(schema));
var tagsValues = new HashSet<string>(); var tagsValues = new HashSet<string>();
var tagsArrays = new List<JArray>(); var tagsArrays = new List<JsonArray>();
GetValues(schema, tagsValues, tagsArrays, datas); GetValues(schema, tagsValues, tagsArrays, datas);
@ -73,14 +73,14 @@ namespace Squidex.Domain.Apps.Core.Tags
{ {
if (denormalized.TryGetValue(array[i].ToString(), out var result)) if (denormalized.TryGetValue(array[i].ToString(), out var result))
{ {
array[i] = result; array[i] = JsonValue.Create(result);
} }
} }
} }
} }
} }
private static void GetValues(Schema schema, HashSet<string> values, List<JArray> arrays, params NamedContentData[] datas) private static void GetValues(Schema schema, HashSet<string> values, List<JsonArray> arrays, params NamedContentData[] datas)
{ {
foreach (var field in schema.Fields) foreach (var field in schema.Fields)
{ {
@ -109,14 +109,12 @@ namespace Squidex.Domain.Apps.Core.Tags
{ {
foreach (var partition in fieldData) 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)) if (nestedObject.TryGetValue(nestedField.Name, out var nestedValue))
{ {
ExtractTags(nestedValue, values, arrays); ExtractTags(nestedValue, values, arrays);
@ -133,19 +131,19 @@ namespace Squidex.Domain.Apps.Core.Tags
} }
} }
private static void ExtractTags(JToken token, ISet<string> values, ICollection<JArray> arrays) private static void ExtractTags(IJsonValue value, ISet<string> values, ICollection<JsonArray> 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);
} }
} }
} }

5
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs

@ -9,11 +9,11 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators; using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1028, IDE0004 // Code must not contain trailing whitespace #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 public sealed class ContentValidator
{ {
private static readonly ContentFieldData DefaultFieldData = new ContentFieldData(); private static readonly ContentFieldData DefaultFieldData = new ContentFieldData();
private static readonly JToken DefaultValue = JValue.CreateNull();
private readonly Schema schema; private readonly Schema schema;
private readonly PartitionResolver partitionResolver; private readonly PartitionResolver partitionResolver;
private readonly ValidationContext context; private readonly ValidationContext context;
@ -96,7 +95,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
var type = isLanguage ? "language" : "invariant value"; var type = isLanguage ? "language" : "invariant value";
return new ObjectValidator<JToken>(fieldsValidators, isPartial, type, DefaultValue); return new ObjectValidator<IJsonValue>(fieldsValidators, isPartial, type, JsonValue.Null);
} }
} }
} }

175
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs

@ -7,45 +7,80 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using NodaTime.Text; using NodaTime.Text;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent namespace Squidex.Domain.Apps.Core.ValidateContent
{ {
public sealed class JsonValueConverter : IFieldVisitor<object> public sealed class JsonValueConverter : IFieldVisitor<object>
{ {
private readonly JToken value; private readonly IJsonValue value;
private JsonValueConverter(JToken value) private JsonValueConverter(IJsonValue value)
{ {
this.value = 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)); return field.Accept(new JsonValueConverter(json));
} }
public object Visit(IArrayField field) public object Visit(IArrayField field)
{ {
return value.ToObject<List<JObject>>(); return ConvertToObjectList();
} }
public object Visit(IField<AssetsFieldProperties> field) public object Visit(IField<AssetsFieldProperties> field)
{ {
return value.ToObject<List<Guid>>(); return ConvertToGuidList();
}
public object Visit(IField<ReferencesFieldProperties> field)
{
return ConvertToGuidList();
}
public object Visit(IField<TagsFieldProperties> field)
{
return ConvertToStringList();
} }
public object Visit(IField<BooleanFieldProperties> field) public object Visit(IField<BooleanFieldProperties> field)
{ {
return (bool?)value; if (value is JsonScalar<bool> b)
{
return b.Value;
}
throw new InvalidCastException("Invalid json type, expected boolean.");
}
public object Visit(IField<NumberFieldProperties> field)
{
if (value is JsonScalar<double> b)
{
return b.Value;
}
throw new InvalidCastException("Invalid json type, expected number.");
}
public object Visit(IField<StringFieldProperties> field)
{
if (value is JsonScalar<string> b)
{
return b.Value;
}
throw new InvalidCastException("Invalid json type, expected string.");
} }
public object Visit(IField<DateTimeFieldProperties> field) public object Visit(IField<DateTimeFieldProperties> field)
{ {
if (value.Type == JTokenType.String) if (value.Type == JsonValueType.String)
{ {
var parseResult = InstantPattern.General.Parse(value.ToString()); var parseResult = InstantPattern.General.Parse(value.ToString());
@ -62,31 +97,49 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public object Visit(IField<GeolocationFieldProperties> field) public object Visit(IField<GeolocationFieldProperties> field)
{ {
var geolocation = (JObject)value; if (value is JsonObject geolocation)
foreach (var property in geolocation.Properties())
{ {
if (!string.Equals(property.Name, "latitude", StringComparison.OrdinalIgnoreCase) && foreach (var propertyName in geolocation.Keys)
!string.Equals(property.Name, "longitude", StringComparison.OrdinalIgnoreCase))
{ {
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"]; if (geolocation.TryGetValue("latitude", out var latValue) && latValue is JsonScalar<double> latNumber)
var lon = (double)geolocation["longitude"]; {
var lat = latNumber.Value;
if (!lat.IsBetween(-90, 90)) if (!lat.IsBetween(-90, 90))
{ {
throw new InvalidCastException("Latitude must be between -90 and 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)) if (geolocation.TryGetValue("longitude", out var lonValue) && lonValue is JsonScalar<double> lonNumber)
{ {
throw new InvalidCastException("Longitude must be between -180 and 180."); 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<JsonFieldProperties> field) public object Visit(IField<JsonFieldProperties> field)
@ -94,24 +147,76 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return value; return value;
} }
public object Visit(IField<NumberFieldProperties> field) private object ConvertToGuidList()
{ {
return (double?)value; if (value is JsonArray array)
} {
var result = new List<Guid>();
public object Visit(IField<ReferencesFieldProperties> field) foreach (var item in array)
{ {
return value.ToObject<List<Guid>>(); if (item is JsonScalar<string> 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<StringFieldProperties> field) private object ConvertToStringList()
{ {
return value.ToString(); if (value is JsonArray array)
{
var result = new List<string>();
foreach (var item in array)
{
if (item is JsonScalar<string> 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<TagsFieldProperties> field) private object ConvertToObjectList()
{ {
return value.ToObject<List<string>>(); if (value is JsonArray array)
{
var result = new List<JsonObject>();
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.");
} }
} }
} }

46
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs

@ -10,13 +10,20 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Core.ValidateContent namespace Squidex.Domain.Apps.Core.ValidateContent
{ {
public delegate Task<IReadOnlyList<Guid>> CheckContents(Guid schemaId, FilterNode filter);
public delegate Task<IReadOnlyList<IAssetInfo>> CheckAssets(IEnumerable<Guid> ids);
public sealed class ValidationContext public sealed class ValidationContext
{ {
private readonly Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent; private readonly Guid contentId;
private readonly Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset; private readonly Guid schemaId;
private readonly CheckContents checkContent;
private readonly CheckAssets checkAsset;
private readonly ImmutableQueue<string> propertyPath; private readonly ImmutableQueue<string> propertyPath;
public ImmutableQueue<string> Path public ImmutableQueue<string> Path
@ -24,18 +31,32 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
get { return propertyPath; } get { return propertyPath; }
} }
public Guid ContentId
{
get { return contentId; }
}
public Guid SchemaId
{
get { return schemaId; }
}
public bool IsOptional { get; } public bool IsOptional { get; }
public ValidationContext( public ValidationContext(
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent, Guid contentId,
Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset) Guid schemaId,
: this(checkContent, checkAsset, ImmutableQueue<string>.Empty, false) CheckContents checkContent,
CheckAssets checkAsset)
: this(contentId, schemaId, checkContent, checkAsset, ImmutableQueue<string>.Empty, false)
{ {
} }
private ValidationContext( private ValidationContext(
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent, Guid contentId,
Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset, Guid schemaId,
CheckContents checkContent,
CheckAssets checkAsset,
ImmutableQueue<string> propertyPath, ImmutableQueue<string> propertyPath,
bool isOptional) bool isOptional)
{ {
@ -46,23 +67,26 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
this.checkContent = checkContent; this.checkContent = checkContent;
this.checkAsset = checkAsset; this.checkAsset = checkAsset;
this.contentId = contentId;
this.schemaId = schemaId;
IsOptional = isOptional; IsOptional = isOptional;
} }
public ValidationContext Optional(bool 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) 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<IReadOnlyList<Guid>> GetInvalidContentIdsAsync(IEnumerable<Guid> contentIds, Guid schemaId) public Task<IReadOnlyList<Guid>> GetContentIdsAsync(Guid schemaId, FilterNode filter)
{ {
return checkContent(contentIds, schemaId); return checkContent(schemaId, filter);
} }
public Task<IReadOnlyList<IAssetInfo>> GetAssetInfosAsync(IEnumerable<Guid> assetId) public Task<IReadOnlyList<IAssetInfo>> GetAssetInfosAsync(IEnumerable<Guid> assetId)

8
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -14,9 +15,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
public sealed class AllowedValuesValidator<T> : IValidator public sealed class AllowedValuesValidator<T> : IValidator
{ {
private readonly T[] allowedValues; private readonly IEnumerable<T> allowedValues;
public AllowedValuesValidator(params T[] allowedValues) public AllowedValuesValidator(params T[] allowedValues)
: this((IEnumerable<T>)allowedValues)
{
}
public AllowedValuesValidator(IEnumerable<T> allowedValues)
{ {
Guard.NotNull(allowedValues, nameof(allowedValues)); Guard.NotNull(allowedValues, nameof(allowedValues));

7
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs

@ -7,9 +7,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
@ -30,9 +29,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
object typedValue = null; 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<Task>(); var tasks = new List<Task>();

2
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) public async Task ValidateAsync(object value, ValidationContext context, AddError addError)
{ {
if (value is IDictionary<string, TValue> values) if (value is IReadOnlyDictionary<string, TValue> values)
{ {
foreach (var fieldData in values) foreach (var fieldData in values)
{ {

15
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs

@ -7,12 +7,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
public sealed class ReferencesValidator : IValidator public sealed class ReferencesValidator : IValidator
{ {
private static readonly IReadOnlyList<string> Path = new List<string> { "Id" };
private readonly Guid schemaId; private readonly Guid schemaId;
public ReferencesValidator(Guid schemaId) public ReferencesValidator(Guid schemaId)
@ -24,11 +28,16 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (value is ICollection<Guid> contentIds) if (value is ICollection<Guid> 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}'.");
}
} }
} }
} }

51
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<string> Path(ValidationContext context)
{
return Enumerable.Repeat("Data", 1).Union(context.Path).ToList();
}
}
}

23
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidatorsFactory.cs

@ -8,11 +8,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json.Linq;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators; using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent 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)); nestedSchema[nestedField.Name] = (false, new FieldValidator(nestedField.Accept(this).ToArray(), nestedField));
} }
yield return new CollectionItemValidator(new ObjectValidator<JToken>(nestedSchema, false, "field", JValue.CreateNull())); yield return new CollectionItemValidator(new ObjectValidator<IJsonValue>(nestedSchema, false, "field", JsonValue.Null));
} }
public IEnumerable<IValidator> Visit(IField<AssetsFieldProperties> field) public IEnumerable<IValidator> Visit(IField<AssetsFieldProperties> field)
@ -109,7 +109,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
if (field.Properties.AllowedValues != null) if (field.Properties.AllowedValues != null)
{ {
yield return new AllowedValuesValidator<double>(field.Properties.AllowedValues.ToArray()); yield return new AllowedValuesValidator<double>(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) if (field.Properties.AllowedValues != null)
{ {
yield return new AllowedValuesValidator<string>(field.Properties.AllowedValues.ToArray()); yield return new AllowedValuesValidator<string>(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); yield return new CollectionValidator(field.Properties.IsRequired, field.Properties.MinItems, field.Properties.MaxItems);
} }
if (field.Properties.AllowedValues != null)
{
yield return new CollectionItemValidator(new AllowedValuesValidator<string>(field.Properties.AllowedValues));
}
yield return new CollectionItemValidator(new RequiredStringValidator()); yield return new CollectionItemValidator(new RequiredStringValidator());
} }
} }

13
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.ConvertContent;
using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
@ -22,24 +23,24 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return data.GetReferencedIds(schema).ToList(); return data.GetReferencedIds(schema).ToList();
} }
public static NamedContentData FromMongoModel(this IdContentData result, Schema schema, List<Guid> deletedIds) public static NamedContentData FromMongoModel(this IdContentData result, Schema schema, List<Guid> deletedIds, IJsonSerializer serializer)
{ {
return result.ConvertId2Name(schema, return result.ConvertId2Name(schema,
FieldConverters.ForValues( FieldConverters.ForValues(
ValueConverters.DecodeJson(), ValueConverters.DecodeJson(serializer),
ValueReferencesConverter.CleanReferences(deletedIds)), ValueReferencesConverter.CleanReferences(deletedIds)),
FieldConverters.ForNestedId2Name( FieldConverters.ForNestedId2Name(
ValueConverters.DecodeJson(), ValueConverters.DecodeJson(serializer),
ValueReferencesConverter.CleanReferences(deletedIds))); 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, return result.ConvertName2Id(schema,
FieldConverters.ForValues( FieldConverters.ForValues(
ValueConverters.EncodeJson()), ValueConverters.EncodeJson(serializer)),
FieldConverters.ForNestedName2Id( FieldConverters.ForNestedName2Id(
ValueConverters.EncodeJson())); ValueConverters.EncodeJson(serializer)));
} }
} }
} }

13
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.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
@ -26,10 +27,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
private readonly string collectionName; private readonly string collectionName;
public MongoContentCollection(IMongoDatabase database, string collectionName) protected IJsonSerializer Serializer { get; }
public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer, string collectionName)
: base(database) : base(database)
{ {
this.collectionName = collectionName; this.collectionName = collectionName;
Serializer = serializer;
} }
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default(CancellationToken)) protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default(CancellationToken))
@ -49,7 +54,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
query = query.AdjustToModel(schema.SchemaDef, useDraft); 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 contentCount = Collection.Find(filter).CountDocumentsAsync();
var contentItems = var contentItems =
@ -64,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
foreach (var entity in contentItems.Result) foreach (var entity in contentItems.Result)
{ {
entity.ParseData(schema.SchemaDef); entity.ParseData(schema.SchemaDef, Serializer);
} }
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result); return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result);
@ -96,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
foreach (var entity in contentItems.Result) foreach (var entity in contentItems.Result)
{ {
entity.ParseData(schema.SchemaDef); entity.ParseData(schema.SchemaDef, Serializer);
} }
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result); return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result);

19
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.Apps;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -26,8 +29,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
internal sealed class MongoContentDraftCollection : MongoContentCollection internal sealed class MongoContentDraftCollection : MongoContentCollection
{ {
public MongoContentDraftCollection(IMongoDatabase database) public MongoContentDraftCollection(IMongoDatabase database, IJsonSerializer serializer)
: base(database, "State_Content_Draft") : base(database, serializer, "State_Content_Draft")
{ {
} }
@ -52,13 +55,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await base.SetupCollectionAsync(collection, ct); await base.SetupCollectionAsync(collection, ct);
} }
public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> ids) public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, ISchemaEntity schema, FilterNode filterNode)
{ {
var filter = filterNode.AdjustToModel(schema.SchemaDef, true).ToFilter(schema.Id);
var contentEntities = 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(); .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<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId) public async Task<IReadOnlyList<Guid>> 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) await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id && x.IsDeleted != true).Not(x => x.DataText)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef); contentEntity?.ParseData(schema.SchemaDef, Serializer);
return contentEntity; return contentEntity;
} }
@ -103,7 +108,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId); 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); return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
} }

9
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.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
@ -24,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
private NamedContentData dataDraft; private NamedContentData dataDraft;
[BsonId] [BsonId]
[BsonElement] [BsonElement("_id")]
[BsonRepresentation(BsonType.String)] [BsonRepresentation(BsonType.String)]
public Guid Id { get; set; } public Guid Id { get; set; }
@ -124,13 +125,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
get { return dataDraft; } 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) if (DataDraftByIds != null)
{ {
dataDraft = DataDraftByIds.FromMongoModel(schema, ReferencedIdsDeleted); dataDraft = DataDraftByIds.FromMongoModel(schema, ReferencedIdsDeleted, serializer);
} }
} }
} }

7
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.Apps;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
internal sealed class MongoContentPublishedCollection : MongoContentCollection internal sealed class MongoContentPublishedCollection : MongoContentCollection
{ {
public MongoContentPublishedCollection(IMongoDatabase database) public MongoContentPublishedCollection(IMongoDatabase database, IJsonSerializer serializer)
: base(database, "State_Content_Published") : 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) await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id).Not(x => x.DataText)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef); contentEntity?.ParseData(schema.SchemaDef, Serializer);
return contentEntity; return contentEntity;
} }

15
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.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
@ -26,17 +27,21 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
private readonly IMongoDatabase database; private readonly IMongoDatabase database;
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;
private readonly IJsonSerializer serializer;
private readonly MongoContentDraftCollection contentsDraft; private readonly MongoContentDraftCollection contentsDraft;
private readonly MongoContentPublishedCollection contentsPublished; 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(appProvider, nameof(appProvider));
Guard.NotNull(serializer, nameof(serializer));
this.appProvider = appProvider; this.appProvider = appProvider;
contentsDraft = new MongoContentDraftCollection(database); this.serializer = serializer;
contentsPublished = new MongoContentPublishedCollection(database);
contentsDraft = new MongoContentDraftCollection(database, serializer);
contentsPublished = new MongoContentPublishedCollection(database, serializer);
this.database = database; this.database = database;
} }
@ -91,11 +96,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<IReadOnlyList<Guid>> QueryNotFoundAsync(Guid appId, Guid schemaId, IList<Guid> ids) public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode filterNode)
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
return await contentsDraft.QueryNotFoundAsync(appId, schemaId, ids); return await contentsDraft.QueryIdsAsync(appId, await appProvider.GetSchemaAsync(appId, schemaId), filterNode);
} }
} }

4
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 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; var idDraftData = idData;
if (!ReferenceEquals(value.Data, value.DataDraft)) 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 var content = SimpleMapper.Map(value, new MongoContentEntity

50
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<string>, IReadOnlyList<string>> pathConverter;
public AdaptionVisitor(Func<IReadOnlyList<string>, IReadOnlyList<string>> 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<Guid> guidList)
{
result = new FilterComparison(nodeIn.Lhs, nodeIn.Operator, new FilterValue(guidList.Select(x => x.ToString()).ToList()));
}
return result;
}
}
}

66
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs

@ -11,7 +11,6 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver; using MongoDB.Driver;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.GenerateEdmSchema; using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
@ -28,33 +27,30 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
typeof(MongoContentEntity).GetProperties() typeof(MongoContentEntity).GetProperties()
.ToDictionary(x => x.Name, x => x.GetCustomAttribute<BsonElementAttribute>()?.ElementName ?? x.Name, StringComparer.OrdinalIgnoreCase); .ToDictionary(x => x.Name, x => x.GetCustomAttribute<BsonElementAttribute>()?.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<string>, IReadOnlyList<string>> pathConverter; var pathConverter = PathConverter(schema, useDraft);
public AdaptionVisitor(Func<IReadOnlyList<string>, IReadOnlyList<string>> pathConverter) if (query.Filter != null)
{ {
this.pathConverter = pathConverter; query.Filter = query.Filter.Accept(new AdaptionVisitor(pathConverter));
} }
public override FilterNode Visit(FilterComparison nodeIn) query.Sort = query.Sort.Select(x => new SortNode(pathConverter(x.Path), x.SortOrder)).ToList();
{
var value = nodeIn.Rhs.Value;
if (value is Instant && return query;
!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 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<string>, IReadOnlyList<string>> PathConverter(Schema schema, bool useDraft)
{ {
var pathConverter = new Func<IReadOnlyList<string>, IReadOnlyList<string>>(propertyNames => return new Func<IReadOnlyList<string>, IReadOnlyList<string>>(propertyNames =>
{ {
var result = new List<string>(propertyNames); var result = new List<string>(propertyNames);
@ -96,15 +92,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
return result; 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<MongoContentEntity, MongoContentEntity> ContentSort(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, Query query) public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentSort(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, Query query)
@ -122,16 +109,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
return cursor.Skip(query); return cursor.Skip(query);
} }
public static FilterDefinition<MongoContentEntity> BuildQuery(Query query, Guid schemaId, Status[] status) public static FilterDefinition<MongoContentEntity> ToFilter(this Query query, Guid schemaId, Status[] status)
{ {
var filters = new List<FilterDefinition<MongoContentEntity>> var filters = new List<FilterDefinition<MongoContentEntity>>
{ {
Filter.Eq(x => x.IndexedSchemaId, schemaId) Filter.Eq(x => x.IndexedSchemaId, schemaId),
Filter.Ne(x => x.IsDeleted, true)
}; };
if (status != null) if (status != null)
{ {
filters.Add(Filter.Ne(x => x.IsDeleted, true));
filters.Add(Filter.In(x => x.Status, status)); 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 Filter.And(filters);
{ }
return filters[0];
} public static FilterDefinition<MongoContentEntity> ToFilter(this FilterNode filterNode, Guid schemaId)
else {
var filters = new List<FilterDefinition<MongoContentEntity>>
{ {
return Filter.And(filters); Filter.Eq(x => x.IndexedSchemaId, schemaId),
} Filter.Ne(x => x.IsDeleted, true),
filterNode.BuildFilter<MongoContentEntity>()
};
return Filter.And(filters);
} }
} }
} }

18
src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs

@ -8,8 +8,8 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
[CollectionName("UISettings")] [CollectionName("UISettings")]
public sealed class State public sealed class State
{ {
public JObject Settings { get; set; } = new JObject(); public JsonObject Settings { get; set; } = JsonValue.Object();
} }
public AppUISettingsGrain(IStore<Guid> store) public AppUISettingsGrain(IStore<Guid> store)
@ -41,19 +41,19 @@ namespace Squidex.Domain.Apps.Entities.Apps
return persistence.ReadAsync(); return persistence.ReadAsync();
} }
public Task<J<JObject>> GetAsync() public Task<J<JsonObject>> GetAsync()
{ {
return Task.FromResult(state.Settings.AsJ()); return Task.FromResult(state.Settings.AsJ());
} }
public Task SetAsync(J<JObject> settings) public Task SetAsync(J<JsonObject> settings)
{ {
state.Settings = settings; state.Settings = settings;
return persistence.WriteSnapshotAsync(state); return persistence.WriteSnapshotAsync(state);
} }
public Task SetAsync(string path, J<JToken> value) public Task SetAsync(string path, J<IJsonValue> value)
{ {
var container = GetContainer(path, out var key); 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."); throw new InvalidOperationException("Path does not lead to an object.");
} }
container[key] = value; container[key] = value.Value;
return persistence.WriteSnapshotAsync(state); return persistence.WriteSnapshotAsync(state);
} }
@ -79,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
return persistence.WriteSnapshotAsync(state); 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)); Guard.NotNullOrEmpty(path, nameof(path));
@ -95,12 +95,12 @@ namespace Squidex.Domain.Apps.Entities.Apps
{ {
if (!current.TryGetValue(segment, out var temp)) if (!current.TryGetValue(segment, out var temp))
{ {
temp = new JObject(); temp = JsonValue.Object();
current[segment] = temp; current[segment] = temp;
} }
if (temp is JObject next) if (temp is JsonObject next)
{ {
current = next; current = next;
} }

19
src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs

@ -8,7 +8,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Orleans; using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Domain.Apps.Entities.Backup; using Squidex.Domain.Apps.Entities.Backup;
@ -16,6 +15,8 @@ using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Shared.Users; using Squidex.Shared.Users;
@ -27,6 +28,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
private const string SettingsFile = "Settings.json"; private const string SettingsFile = "Settings.json";
private readonly IGrainFactory grainFactory; private readonly IGrainFactory grainFactory;
private readonly IUserResolver userResolver; private readonly IUserResolver userResolver;
private readonly IJsonSerializer serializer;
private readonly IAppsByNameIndex appsByNameIndex; private readonly IAppsByNameIndex appsByNameIndex;
private readonly HashSet<string> contributors = new HashSet<string>(); private readonly HashSet<string> contributors = new HashSet<string>();
private Dictionary<string, string> usersWithEmail = new Dictionary<string, string>(); private Dictionary<string, string> usersWithEmail = new Dictionary<string, string>();
@ -36,13 +38,14 @@ namespace Squidex.Domain.Apps.Entities.Apps
public override string Name { get; } = "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(grainFactory, nameof(grainFactory));
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(userResolver, nameof(userResolver)); Guard.NotNull(userResolver, nameof(userResolver));
this.grainFactory = grainFactory; this.grainFactory = grainFactory;
this.serializer = serializer;
this.userResolver = userResolver; this.userResolver = userResolver;
appsByNameIndex = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id); appsByNameIndex = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id);
@ -162,14 +165,14 @@ namespace Squidex.Domain.Apps.Entities.Apps
private async Task ReadUsersAsync(BackupReader reader) private async Task ReadUsersAsync(BackupReader reader)
{ {
var json = await reader.ReadJsonAttachmentAsync(UsersFile); var json = await reader.ReadJsonAttachmentAsync<Dictionary<string, string>>(UsersFile);
usersWithEmail = json.ToObject<Dictionary<string, string>>(); usersWithEmail = json;
} }
private async Task WriteUsersAsync(BackupWriter writer) private async Task WriteUsersAsync(BackupWriter writer)
{ {
var json = JObject.FromObject(usersWithEmail); var json = usersWithEmail;
await writer.WriteJsonAsync(UsersFile, json); await writer.WriteJsonAsync(UsersFile, json);
} }
@ -183,9 +186,9 @@ namespace Squidex.Domain.Apps.Entities.Apps
private async Task ReadSettingsAsync(BackupReader reader, Guid appId) private async Task ReadSettingsAsync(BackupReader reader, Guid appId)
{ {
var json = await reader.ReadJsonAttachmentAsync(SettingsFile); var json = await reader.ReadJsonAttachmentAsync<JsonObject>(SettingsFile);
await grainFactory.GetGrain<IAppUISettingsGrain>(appId).SetAsync((JObject)json); await grainFactory.GetGrain<IAppUISettingsGrain>(appId).SetAsync(json);
} }
public override async Task CompleteRestoreAsync(Guid appId, BackupReader reader) public override async Task CompleteRestoreAsync(Guid appId, BackupReader reader)

6
src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Orleans; using Orleans;
@ -19,6 +20,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics
{ {
private readonly IAppsByNameIndex index; private readonly IAppsByNameIndex index;
public IEnumerable<string> Scopes
{
get { yield return HealthCheckScopes.Cluster; }
}
public OrleansAppsHealthCheck(IGrainFactory grainFactory) public OrleansAppsHealthCheck(IGrainFactory grainFactory)
{ {
Guard.NotNull(grainFactory, nameof(grainFactory)); Guard.NotNull(grainFactory, nameof(grainFactory));

8
src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs

@ -6,19 +6,19 @@
// ========================================================================== // ==========================================================================
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Orleans; using Orleans;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps namespace Squidex.Domain.Apps.Entities.Apps
{ {
public interface IAppUISettingsGrain : IGrainWithGuidKey public interface IAppUISettingsGrain : IGrainWithGuidKey
{ {
Task<J<JObject>> GetAsync(); Task<J<JsonObject>> GetAsync();
Task SetAsync(string path, J<JToken> value); Task SetAsync(string path, J<IJsonValue> value);
Task SetAsync(J<JObject> settings); Task SetAsync(J<JsonObject> settings);
Task RemoveAsync(string path); Task RemoveAsync(string path);
} }

18
src/Squidex.Domain.Apps.Entities/Apps/State/AppState.cs

@ -5,7 +5,7 @@
// All rights reserved. Licensed under the MIT license. // 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.Core.Apps;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Apps; using Squidex.Domain.Apps.Events.Apps;
@ -19,28 +19,28 @@ namespace Squidex.Domain.Apps.Entities.Apps.State
[CollectionName("Apps")] [CollectionName("Apps")]
public class AppState : DomainObjectState<AppState>, IAppEntity public class AppState : DomainObjectState<AppState>, IAppEntity
{ {
[JsonProperty] [DataMember]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty] [DataMember]
public Roles Roles { get; set; } = Roles.Empty; public Roles Roles { get; set; } = Roles.Empty;
[JsonProperty] [DataMember]
public AppPlan Plan { get; set; } public AppPlan Plan { get; set; }
[JsonProperty] [DataMember]
public AppClients Clients { get; set; } = AppClients.Empty; public AppClients Clients { get; set; } = AppClients.Empty;
[JsonProperty] [DataMember]
public AppPatterns Patterns { get; set; } = AppPatterns.Empty; public AppPatterns Patterns { get; set; } = AppPatterns.Empty;
[JsonProperty] [DataMember]
public AppContributors Contributors { get; set; } = AppContributors.Empty; public AppContributors Contributors { get; set; } = AppContributors.Empty;
[JsonProperty] [DataMember]
public LanguagesConfig LanguagesConfig { get; set; } = LanguagesConfig.English; public LanguagesConfig LanguagesConfig { get; set; } = LanguagesConfig.English;
[JsonProperty] [DataMember]
public bool IsArchived { get; set; } public bool IsArchived { get; set; }
protected void On(AppCreated @event) protected void On(AppCreated @event)

4
src/Squidex.Domain.Apps.Entities/Apps/Templates/Builders/StringFieldBuilder.cs

@ -5,9 +5,9 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Immutable;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders 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) public StringFieldBuilder AsDropDown(params string[] values)
{ {
Properties<StringFieldProperties>().AllowedValues = ImmutableList.Create(values); Properties<StringFieldProperties>().AllowedValues = ReadOnlyCollection.Create(values);
Properties<StringFieldProperties>().Editor = StringFieldEditor.Dropdown; Properties<StringFieldProperties>().Editor = StringFieldEditor.Dropdown;
return this; return this;

6
src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs

@ -34,7 +34,9 @@ namespace Squidex.Domain.Apps.Entities.Assets
public async Task<long> GetTotalSizeAsync(Guid appId) public async Task<long> 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(); return (long)entries.Select(x => x.Counters.Get(CounterTotalSize)).FirstOrDefault();
} }
@ -43,7 +45,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
var enriched = new List<AssetStats>(); var enriched = new List<AssetStats>();
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)) for (var date = fromDate; date <= toDate; date = date.AddDays(1))
{ {

7
src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker_EventHandling.cs

@ -60,11 +60,16 @@ namespace Squidex.Domain.Apps.Entities.Assets
[CounterTotalCount] = count [CounterTotalCount] = count
}; };
var key = appId.ToString(); var key = GetKey(appId);
return Task.WhenAll( return Task.WhenAll(
usageStore.TrackUsagesAsync(new UsageUpdate(date, key, Category, counters)), usageStore.TrackUsagesAsync(new UsageUpdate(date, key, Category, counters)),
usageStore.TrackUsagesAsync(new UsageUpdate(SummaryDate, key, Category, counters))); usageStore.TrackUsagesAsync(new UsageUpdate(SummaryDate, key, Category, counters)));
} }
private static string GetKey(Guid appId)
{
return $"{appId}_Assets";
}
} }
} }

7
src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs

@ -8,7 +8,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Tags; using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets.State; 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) private async Task RestoreTagsAsync(Guid appId, BackupReader reader)
{ {
var tags = await reader.ReadJsonAttachmentAsync(TagsFile); var tags = await reader.ReadJsonAttachmentAsync<TagSet>(TagsFile);
await tagService.RebuildTagsAsync(appId, TagGroups.Assets, tags.ToObject<TagSet>()); await tagService.RebuildTagsAsync(appId, TagGroups.Assets, tags);
} }
private async Task BackupTagsAsync(Guid appId, BackupWriter writer) private async Task BackupTagsAsync(Guid appId, BackupWriter writer)
{ {
var tags = await tagService.GetExportableTagsAsync(appId, TagGroups.Assets); 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) private Task WriteAssetAsync(Guid assetId, long fileVersion, BackupWriter writer)

24
src/Squidex.Domain.Apps.Entities/Assets/State/AssetState.cs

@ -7,7 +7,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using System.Runtime.Serialization;
using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
@ -20,37 +20,37 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
{ {
public class AssetState : DomainObjectState<AssetState>, IAssetEntity public class AssetState : DomainObjectState<AssetState>, IAssetEntity
{ {
[JsonProperty] [DataMember]
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }
[JsonProperty] [DataMember]
public string FileName { get; set; } public string FileName { get; set; }
[JsonProperty] [DataMember]
public string MimeType { get; set; } public string MimeType { get; set; }
[JsonProperty] [DataMember]
public long FileVersion { get; set; } public long FileVersion { get; set; }
[JsonProperty] [DataMember]
public long FileSize { get; set; } public long FileSize { get; set; }
[JsonProperty] [DataMember]
public long TotalSize { get; set; } public long TotalSize { get; set; }
[JsonProperty] [DataMember]
public bool IsImage { get; set; } public bool IsImage { get; set; }
[JsonProperty] [DataMember]
public int? PixelWidth { get; set; } public int? PixelWidth { get; set; }
[JsonProperty] [DataMember]
public int? PixelHeight { get; set; } public int? PixelHeight { get; set; }
[JsonProperty] [DataMember]
public bool IsDeleted { get; set; } public bool IsDeleted { get; set; }
[JsonProperty] [DataMember]
public HashSet<string> Tags { get; set; } public HashSet<string> Tags { get; set; }
Guid IAssetInfo.AssetId Guid IAssetInfo.AssetId

7
src/Squidex.Domain.Apps.Entities/Backup/BackupGrain.cs

@ -18,6 +18,7 @@ using Squidex.Domain.Apps.Events;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
@ -34,6 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private readonly IBackupArchiveLocation backupArchiveLocation; private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly IClock clock; private readonly IClock clock;
private readonly IEnumerable<BackupHandler> handlers; private readonly IEnumerable<BackupHandler> handlers;
private readonly IJsonSerializer serializer;
private readonly IEventDataFormatter eventDataFormatter; private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly ISemanticLog log; private readonly ISemanticLog log;
@ -51,6 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
IEventStore eventStore, IEventStore eventStore,
IEventDataFormatter eventDataFormatter, IEventDataFormatter eventDataFormatter,
IEnumerable<BackupHandler> handlers, IEnumerable<BackupHandler> handlers,
IJsonSerializer serializer,
ISemanticLog log, ISemanticLog log,
IStore<Guid> store) IStore<Guid> store)
{ {
@ -60,6 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
Guard.NotNull(eventStore, nameof(eventStore)); Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter)); Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter));
Guard.NotNull(handlers, nameof(handlers)); Guard.NotNull(handlers, nameof(handlers));
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(store, nameof(store)); Guard.NotNull(store, nameof(store));
Guard.NotNull(log, nameof(log)); Guard.NotNull(log, nameof(log));
@ -69,6 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
this.eventStore = eventStore; this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter; this.eventDataFormatter = eventDataFormatter;
this.handlers = handlers; this.handlers = handlers;
this.serializer = serializer;
this.store = store; this.store = store;
this.log = log; this.log = log;
} }
@ -139,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{ {
using (var stream = await backupArchiveLocation.OpenStreamAsync(job.Id)) 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 => await eventStore.QueryAsync(async storedEvent =>
{ {

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save