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. 51
      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. 28
      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. 30
      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. 84
      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. 153
      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. 60
      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
commands:
- docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- docker build -t squidex/squidex:dev .
- docker build -t squidex/squidex:dev -t squidex/squidex:dev-$BUILD_NUMBER .
- docker push squidex/squidex:dev
- docker push squidex/squidex:dev-$BUILD_NUMBER
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker:/var/lib/docker
secrets: [ docker_username, docker_password ]
environment:
- BUILD_NUMBER=${DRONE_BUILD_NUMBER}
when:
event: push
branch: [ master ]

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}";
ruleJob.Content = ToPayload(contentEvent);
var json = ToJson(contentEvent);
ruleJob.Content = JObject.Parse(json);
ruleJob.Content["objectID"] = contentId;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -4,25 +4,32 @@
// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Immutable;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Core.Apps
{
public sealed class AppPatterns : DictionaryWrapper<Guid, AppPattern>
public sealed class AppPatterns : ArrayDictionary<Guid, AppPattern>
{
public static readonly AppPatterns Empty = new AppPatterns();
private AppPatterns()
: base(ImmutableDictionary<Guid, AppPattern>.Empty)
{
}
public AppPatterns(ImmutableDictionary<Guid, AppPattern> inner)
: base(inner)
public AppPatterns(KeyValuePair<Guid, AppPattern>[] items)
: base(items)
{
}
[Pure]
public AppPatterns Remove(Guid id)
{
return new AppPatterns(Without(id));
}
[Pure]
@ -30,13 +37,12 @@ namespace Squidex.Domain.Apps.Core.Apps
{
var newPattern = new AppPattern(name, pattern, message);
return new AppPatterns(Inner.Add(id, newPattern));
if (ContainsKey(id))
{
throw new ArgumentException("Id already exists.", nameof(id));
}
[Pure]
public AppPatterns Remove(Guid id)
{
return new AppPatterns(Inner.Remove(id));
return new AppPatterns(With(id, newPattern));
}
[Pure]
@ -50,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Apps
return this;
}
return new AppPatterns(Inner.SetItem(id, appPattern.Update(name, pattern, message)));
return new AppPatterns(With(id, appPattern.Update(name, pattern, message)));
}
}
}

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

@ -7,9 +7,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
@ -31,7 +31,12 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{
var json = serializer.Deserialize<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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{
var json = serializer.Deserialize<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)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Apps.Json
{
@ -30,7 +31,12 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{
var json = serializer.Deserialize<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 Newtonsoft.Json;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
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 Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Security;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Squidex.Domain.Apps.Core.Apps.Json
@ -33,7 +32,12 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{
var json = serializer.Deserialize<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.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Squidex.Infrastructure;
@ -13,7 +14,6 @@ namespace Squidex.Domain.Apps.Core.Apps
{
public sealed class LanguageConfig : IFieldPartitionItem
{
private static readonly Language[] DefaultFallback = new Language[0];
private readonly Language language;
private readonly Language[] languageFallbacks;
@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Core.Apps
IsOptional = isOptional;
this.language = language;
this.languageFallbacks = fallback ?? DefaultFallback;
this.languageFallbacks = fallback ?? Array.Empty<Language>();
}
}
}

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

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

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

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

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

@ -9,7 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Contents
{
@ -61,7 +61,7 @@ namespace Squidex.Domain.Apps.Core.Contents
{
var resultValue = new ContentFieldData();
foreach (var partitionValue in fieldValue.Value.Where(x => !x.Value.IsNull()))
foreach (var partitionValue in fieldValue.Value.Where(x => x.Value.Type != JsonValueType.Null))
{
resultValue[partitionValue.Key] = partitionValue.Value;
}

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

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

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

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

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

@ -5,15 +5,15 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure;
using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Rules.Triggers
{
[TypeName(nameof(ContentChangedTrigger))]
public sealed class ContentChangedTrigger : RuleTrigger
{
public ImmutableList<ContentChangedTriggerSchema> Schemas { get; set; }
public ReadOnlyCollection<ContentChangedTriggerSchema> Schemas { 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.
// ==========================================================================
using Squidex.Infrastructure;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
@ -30,11 +31,19 @@ namespace Squidex.Domain.Apps.Core.Schemas
get { return fields.ByName; }
}
public ArrayField(long id, string name, Partitioning partitioning, ArrayFieldProperties properties)
public ArrayField(long id, string name, Partitioning partitioning, ArrayFieldProperties properties = null, IFieldSettings settings = null)
: base(id, name, partitioning, properties)
{
}
public ArrayField(long id, string name, Partitioning partitioning, NestedField[] fields, ArrayFieldProperties properties = null, IFieldSettings settings = null)
: this(id, name, partitioning, properties)
{
Guard.NotNull(fields, nameof(fields));
this.fields = new FieldCollection<NestedField>(fields);
}
[Pure]
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);
}
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();
}

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

@ -5,8 +5,8 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure;
using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas
{
@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
public int? AspectHeight { get; set; }
public ImmutableList<string> AllowedExtensions { get; set; }
public ReadOnlyCollection<string> AllowedExtensions { get; set; }
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
{
@ -47,14 +47,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
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);
}
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);
}
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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Linq;
using Squidex.Infrastructure;
@ -18,9 +17,12 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
public static readonly FieldCollection<T> Empty = new FieldCollection<T>();
private ImmutableArray<T> fieldsOrdered = ImmutableArray<T>.Empty;
private ImmutableDictionary<long, T> fieldsById;
private ImmutableDictionary<string, T> fieldsByName;
private static readonly Dictionary<long, T> EmptyById = new Dictionary<long, T>();
private static readonly Dictionary<string, T> EmptyByString = new Dictionary<string, T>();
private T[] fieldsOrdered;
private Dictionary<long, T> fieldsById;
private Dictionary<string, T> fieldsByName;
public IReadOnlyList<T> Ordered
{
@ -35,11 +37,11 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
if (fieldsOrdered.Length == 0)
{
fieldsById = ImmutableDictionary<long, T>.Empty;
fieldsById = EmptyById;
}
else
{
fieldsById = fieldsOrdered.ToImmutableDictionary(x => x.Id);
fieldsById = fieldsOrdered.ToDictionary(x => x.Id);
}
}
@ -55,11 +57,11 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
if (fieldsOrdered.Length == 0)
{
fieldsByName = ImmutableDictionary<string, T>.Empty;
fieldsByName = EmptyByString;
}
else
{
fieldsByName = fieldsOrdered.ToImmutableDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
fieldsByName = fieldsOrdered.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
}
}
@ -69,13 +71,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
private FieldCollection()
{
fieldsOrdered = Array.Empty<T>();
}
public FieldCollection(T[] fields)
{
Guard.NotNull(fields, nameof(fields));
fieldsOrdered = ImmutableArray.Create(fields);
fieldsOrdered = fields;
}
protected override void OnCloned()
@ -94,7 +97,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
return Clone(clone =>
{
clone.fieldsOrdered = fieldsOrdered.Remove(field);
clone.fieldsOrdered = fieldsOrdered.Where(x => x.Id != fieldId).ToArray();
});
}
@ -110,7 +113,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
return Clone(clone =>
{
clone.fieldsOrdered = fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id)).ToImmutableArray();
clone.fieldsOrdered = fieldsOrdered.OrderBy(f => ids.IndexOf(f.Id)).ToArray();
});
}
@ -126,7 +129,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
return Clone(clone =>
{
clone.fieldsOrdered = clone.fieldsOrdered.Add(field);
clone.fieldsOrdered = clone.fieldsOrdered.Union(Enumerable.Repeat(field, 1)).ToArray();
});
}
@ -154,7 +157,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
return Clone(clone =>
{
clone.fieldsOrdered = clone.fieldsOrdered.Replace(field, typedField);
clone.fieldsOrdered = clone.fieldsOrdered.Select(x => ReferenceEquals(x, field) ? newField : x).ToArray();
});
}
}

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 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);
return properties.CreateRootField(id, name, partitioning);
return properties.CreateRootField(id, name, partitioning, settings);
}
public NestedField CreateNestedField(long id, string name, FieldProperties properties)
public NestedField CreateNestedField(long id, string name, FieldProperties properties, IFieldSettings settings = null)
{
CheckProperties(properties);
return properties.CreateNestedField(id, name);
return properties.CreateNestedField(id, name, settings);
}
private void CheckProperties(FieldProperties properties)

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

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);
}
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
{
public interface IField
public interface IField : IFieldSettings
{
long Id { get; }
string Name { get; }
bool IsLocked { get; }
bool IsDisabled { get; }
bool IsHidden { get; }
FieldProperties RawProperties { get; }
T Accept<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.
// ==========================================================================
using System.Collections.Generic;
using Newtonsoft.Json;
using Squidex.Infrastructure;
using System;
using System.Linq;
using P = Squidex.Domain.Apps.Core.Partitioning;
namespace Squidex.Domain.Apps.Core.Schemas.Json
{
public sealed class JsonFieldModel
public sealed class JsonFieldModel : IFieldSettings
{
[JsonProperty]
public long Id { get; set; }
@ -34,6 +37,22 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
public FieldProperties Properties { get; set; }
[JsonProperty]
public List<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
{
public sealed class JsonNestedFieldModel
public sealed class JsonNestedFieldModel : IFieldSettings
{
[JsonProperty]
public long Id { get; set; }
@ -25,5 +25,15 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
[JsonProperty]
public FieldProperties Properties { get; set; }
public bool IsLocked
{
get { return false; }
}
public NestedField ToNestedField()
{
return Properties.CreateNestedField(Id, Name, this);
}
}
}

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

@ -5,16 +5,15 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.Schemas.Json
{
public sealed class JsonSchemaModel
{
private static readonly RootField[] Empty = new RootField[0];
[JsonProperty]
public string Name { get; set; }
@ -25,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
public SchemaProperties Properties { get; set; }
[JsonProperty]
public List<JsonFieldModel> Fields { get; set; }
public JsonFieldModel[] Fields { get; set; }
public JsonSchemaModel()
{
@ -38,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
Properties = schema.Properties;
Fields =
schema.Fields.Select(x =>
schema.Fields.ToArray(x =>
new JsonFieldModel
{
Id = x.Id,
@ -49,16 +48,16 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
IsDisabled = x.IsDisabled,
Partitioning = x.Partitioning.Key,
Properties = x.RawProperties
}).ToList();
});
IsPublished = schema.IsPublished;
}
private static List<JsonNestedFieldModel> CreateChildren(IField field)
private static JsonNestedFieldModel[] CreateChildren(IField field)
{
if (field is ArrayField arrayField)
{
return arrayField.Fields.Select(x =>
return arrayField.Fields.ToArray(x =>
new JsonNestedFieldModel
{
Id = x.Id,
@ -66,68 +65,15 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
IsHidden = x.IsHidden,
IsDisabled = x.IsDisabled,
Properties = x.RawProperties
}).ToList();
});
}
return null;
}
public Schema ToSchema(FieldRegistry registry)
{
var fields = Empty;
if (Fields != null)
{
fields = new RootField[Fields.Count];
for (var i = 0; i < fields.Length; i++)
{
var fieldModel = Fields[i];
var parititonKey = new Partitioning(fieldModel.Partitioning);
var field = registry.CreateRootField(fieldModel.Id, fieldModel.Name, parititonKey, fieldModel.Properties);
if (field is ArrayField arrayField && fieldModel.Children?.Count > 0)
{
foreach (var nestedFieldModel in fieldModel.Children)
{
var nestedField = registry.CreateNestedField(nestedFieldModel.Id, nestedFieldModel.Name, nestedFieldModel.Properties);
if (nestedFieldModel.IsHidden)
{
nestedField = nestedField.Hide();
}
if (nestedFieldModel.IsDisabled)
{
nestedField = nestedField.Disable();
}
arrayField = arrayField.AddField(nestedField);
}
field = arrayField;
}
if (fieldModel.IsDisabled)
{
field = field.Disable();
}
if (fieldModel.IsLocked)
{
field = field.Lock();
}
if (fieldModel.IsHidden)
public Schema ToSchema()
{
field = field.Hide();
}
fields[i] = field;
}
}
var fields = Fields.ToArray(f => f.ToField()) ?? Array.Empty<RootField>();
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 Newtonsoft.Json;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Domain.Apps.Core.Schemas.Json
{
public sealed class SchemaConverter : JsonClassConverter<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)
{
serializer.Serialize(writer, new JsonSchemaModel(value));
@ -30,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.Schemas.Json
protected override Schema ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
return serializer.Deserialize<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);
}
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; }
protected NestedField(long id, string name)
protected NestedField(long id, string name, IFieldSettings settings = null)
{
Guard.NotNullOrEmpty(name, nameof(name));
Guard.GreaterThan(id, 0, nameof(id));
fieldId = id;
fieldName = name;
if (settings != null)
{
isLocked = settings.IsLocked;
isHidden = settings.IsHidden;
isDisabled = settings.IsDisabled;
}
}
[Pure]

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

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

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

@ -5,15 +5,15 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure;
using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName("NumberField")]
public sealed class NumberFieldProperties : FieldProperties
{
public ImmutableList<double> AllowedValues { get; set; }
public ReadOnlyCollection<double> AllowedValues { get; set; }
public double? MaxValue { get; set; }
@ -21,6 +21,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
public double? DefaultValue { get; set; }
public bool IsUnique { get; set; }
public bool InlineEditable { get; set; }
public NumberFieldEditor Editor { get; set; }
@ -35,14 +37,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IField<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);
}
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; }
protected RootField(long id, string name, Partitioning partitioning)
protected RootField(long id, string name, Partitioning partitioning, IFieldSettings settings = null)
{
Guard.NotNullOrEmpty(name, nameof(name));
Guard.GreaterThan(id, 0, nameof(id));
@ -61,6 +61,13 @@ namespace Squidex.Domain.Apps.Core.Schemas
fieldName = name;
this.partitioning = partitioning;
if (settings != null)
{
isLocked = settings.IsLocked;
isHidden = settings.IsHidden;
isDisabled = settings.IsDisabled;
}
}
[Pure]

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

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

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

@ -5,20 +5,22 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Immutable;
using Squidex.Infrastructure;
using System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName("StringField")]
public sealed class StringFieldProperties : FieldProperties
{
public ImmutableList<string> AllowedValues { get; set; }
public ReadOnlyCollection<string> AllowedValues { get; set; }
public int? MinLength { get; set; }
public int? MaxLength { get; set; }
public bool IsUnique { get; set; }
public bool InlineEditable { get; set; }
public string DefaultValue { get; set; }
@ -39,14 +41,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
return visitor.Visit((IField<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 System.Collections.ObjectModel;
namespace Squidex.Domain.Apps.Core.Schemas
{
[TypeName("TagsField")]
public sealed class TagsFieldProperties : FieldProperties
{
public ReadOnlyCollection<string> AllowedValues { get; set; }
public int? MinItems { get; set; }
public int? MaxItems { get; set; }
public TagsFieldEditor Editor { get; set; }
public TagsFieldNormalization Normalization { get; set; }
public override T Accept<T>(IFieldPropertiesVisitor<T> visitor)
@ -28,14 +33,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
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.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
@ -41,11 +40,11 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return result;
}
private static void AppendText(JToken value, StringBuilder stringBuilder, int maxFieldLength, string separator, bool allowObjects)
private static void AppendText(IJsonValue value, StringBuilder stringBuilder, int maxFieldLength, string separator, bool allowObjects)
{
if (value?.Type == JTokenType.String)
if (value.Type == JsonValueType.String)
{
var text = ((JValue)value).ToString(CultureInfo.InvariantCulture);
var text = value.ToString();
if (text.Length <= maxFieldLength)
{
@ -57,18 +56,18 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
stringBuilder.Append(text);
}
}
else if (value?.Type == JTokenType.Array)
else if (value is JsonArray array)
{
foreach (var item in value)
foreach (var item in array)
{
AppendText(item, stringBuilder, maxFieldLength, separator, true);
}
}
else if (value?.Type == JTokenType.Object && allowObjects)
else if (value is JsonObject obj && allowObjects)
{
foreach (JProperty property in value)
foreach (var item in obj.Values)
{
AppendText(property.Value, stringBuilder, maxFieldLength, separator, true);
AppendText(item, stringBuilder, maxFieldLength, separator, true);
}
}
}

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

@ -7,10 +7,10 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent
{
@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
languagePreferences = languagePreferences.Union(languageConfig.LanguageFallbacks).ToList();
}
var result = new Dictionary<string, JToken>();
var result = new Dictionary<string, IJsonValue>();
foreach (var fieldValue in content)
{
@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
foreach (var language in languagePreferences)
{
if (fieldData.TryGetValue(language, out var value) && value != null)
if (fieldData.TryGetValue(language, out var value) && value.Type != JsonValueType.Null)
{
result[fieldValue.Key] = value;

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

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

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

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

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

@ -5,12 +5,11 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.EnrichContent
{
@ -56,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant());
if (field.RawProperties.IsRequired || defaultValue.IsNull())
if (field.RawProperties.IsRequired || defaultValue.Type == JsonValueType.Null)
{
return;
}
@ -65,13 +64,13 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
if (!fieldData.TryGetValue(key, out var value) || ShouldApplyDefaultValue(field, value))
{
fieldData.AddValue(key, defaultValue);
fieldData.AddJsonValue(key, defaultValue);
}
}
private static bool ShouldApplyDefaultValue(IField field, JToken value)
private static bool ShouldApplyDefaultValue(IField field, IJsonValue value)
{
return value.IsNull() || (field is IField<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 Newtonsoft.Json.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.EnrichContent
{
public sealed class DefaultValueFactory : IFieldVisitor<JToken>
public sealed class DefaultValueFactory : IFieldVisitor<IJsonValue>
{
private readonly Instant now;
@ -22,71 +22,71 @@ namespace Squidex.Domain.Apps.Core.EnrichContent
this.now = now;
}
public static JToken CreateDefaultValue(IField field, Instant now)
public static IJsonValue CreateDefaultValue(IField field, Instant now)
{
Guard.NotNull(field, nameof(field));
return field.Accept(new DefaultValueFactory(now));
}
public JToken Visit(IArrayField field)
public IJsonValue Visit(IArrayField field)
{
return new JArray();
return JsonValue.Array();
}
public JToken Visit(IField<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)
{
return now.ToString();
return JsonValue.Create(now.ToString());
}
if (field.Properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Today)
{
return now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
return JsonValue.Create(now.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
}
return field.Properties.DefaultValue?.ToString();
return JsonValue.Create(field.Properties.DefaultValue?.ToString());
}
}
}

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.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
continue;
}
foreach (var partitionValue in fieldData.Where(x => !x.Value.IsNull()))
foreach (var partitionValue in fieldData.Where(x => x.Value.Type != JsonValueType.Null))
{
var ids = field.ExtractReferences(partitionValue.Value);

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

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

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

@ -7,22 +7,21 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
public static class ReferencesExtensions
{
public static IEnumerable<Guid> ExtractReferences(this IField field, JToken value)
public static IEnumerable<Guid> ExtractReferences(this IField field, IJsonValue 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;
}
@ -30,31 +29,27 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
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)
{
result.Add(new JValue(id));
result.Add(JsonValue.Create(id.ToString()));
}
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>();
foreach (var id in ids)
{
if (id.Type == JTokenType.Guid)
foreach (var id in array)
{
result.Add((Guid)id);
}
else if (id.Type == JTokenType.String && Guid.TryParse((string)id, out var guid))
if (id.Type == JsonValueType.String && Guid.TryParse(id.ToString(), out var guid))
{
result.Add(guid);
}

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

@ -8,21 +8,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
public sealed class ReferencesExtractor : IFieldVisitor<IEnumerable<Guid>>
{
private readonly JToken value;
private readonly IJsonValue value;
private ReferencesExtractor(JToken value)
private ReferencesExtractor(IJsonValue 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));
}
@ -31,9 +31,9 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{
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)
{

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

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

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

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

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

@ -7,7 +7,6 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules;
@ -17,8 +16,10 @@ namespace Squidex.Domain.Apps.Core.HandleRules
{
Type ActionType { get; }
Task<(string Description, JObject Data)> CreateJobAsync(EnrichedEvent @event, RuleAction action);
Type DataType { get; }
Task<(string Dump, Exception Exception)> ExecuteJobAsync(JObject data);
Task<(string Description, object Data)> CreateJobAsync(EnrichedEvent @event, RuleAction action);
Task<(string Dump, Exception Exception)> ExecuteJobAsync(object data);
}
}

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

@ -7,7 +7,6 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Infrastructure;
@ -25,29 +24,24 @@ namespace Squidex.Domain.Apps.Core.HandleRules
get { return typeof(TAction); }
}
protected RuleActionHandler(RuleEventFormatter formatter)
Type IRuleActionHandler.DataType
{
Guard.NotNull(formatter, nameof(formatter));
this.formatter = formatter;
get { return typeof(TData); }
}
protected virtual string ToPayloadJson<T>(T @event)
protected RuleActionHandler(RuleEventFormatter formatter)
{
return formatter.ToPayload(@event).ToString();
}
Guard.NotNull(formatter, nameof(formatter));
protected virtual string ToEnvelopeJson(EnrichedEvent @event)
{
return formatter.ToEnvelope(@event).ToString();
this.formatter = formatter;
}
protected virtual JObject ToPayload<T>(T @event)
protected virtual string ToJson<T>(T @event)
{
return formatter.ToPayload(@event);
}
protected virtual JObject ToEnvelope(EnrichedEvent @event)
protected virtual string ToEnvelopeJson(EnrichedEvent @event)
{
return formatter.ToEnvelope(@event);
}
@ -62,16 +56,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return formatter.Format(text, @event);
}
async Task<(string Description, JObject Data)> IRuleActionHandler.CreateJobAsync(EnrichedEvent @event, RuleAction action)
async Task<(string Description, object Data)> IRuleActionHandler.CreateJobAsync(EnrichedEvent @event, RuleAction action)
{
var (description, data) = await CreateJobAsync(@event, (TAction)action);
return (description, JObject.FromObject(data));
return (description, data);
}
async Task<(string Dump, Exception Exception)> IRuleActionHandler.ExecuteJobAsync(JObject data)
async Task<(string Dump, Exception Exception)> IRuleActionHandler.ExecuteJobAsync(object data)
{
var typedData = data.ToObject<TData>();
var typedData = (TData)data;
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.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Shared.Users;
namespace Squidex.Domain.Apps.Core.HandleRules
@ -27,15 +27,15 @@ namespace Squidex.Domain.Apps.Core.HandleRules
private static readonly Regex ContentDataPlaceholderOld = new Regex(@"^CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}", RegexOptions.Compiled);
private static readonly Regex ContentDataPlaceholderNew = new Regex(@"^\{CONTENT_DATA(\.([0-9A-Za-z\-_]*)){2,}\}", RegexOptions.Compiled);
private readonly List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)> patterns = new List<(char[] Pattern, Func<EnrichedEvent, string> Replacer)>();
private readonly JsonSerializer serializer;
private readonly IJsonSerializer jsonSerializer;
private readonly IRuleUrlGenerator urlGenerator;
public RuleEventFormatter(JsonSerializer serializer, IRuleUrlGenerator urlGenerator)
public RuleEventFormatter(IJsonSerializer jsonSerializer, IRuleUrlGenerator urlGenerator)
{
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
Guard.NotNull(urlGenerator, nameof(urlGenerator));
this.serializer = serializer;
this.jsonSerializer = jsonSerializer;
this.urlGenerator = urlGenerator;
AddPattern("APP_ID", AppId);
@ -55,17 +55,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules
patterns.Add((placeholder.ToCharArray(), generator));
}
public virtual JObject ToPayload<T>(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(
new JProperty("type", @event.Name),
new JProperty("payload", ToPayload(@event)),
new JProperty("timestamp", @event.Timestamp.ToString()));
return jsonSerializer.Serialize(new { type = @event.Name, payload = @event, timestamp = @event.Timestamp });
}
public string Format(string text, EnrichedEvent @event)
@ -264,14 +261,14 @@ namespace Squidex.Domain.Apps.Core.HandleRules
for (var j = 2; j < path.Length; j++)
{
if (value is JObject obj && obj.TryGetValue(path[j], out value))
if (value is JsonObject obj && obj.TryGetValue(path[j], out value))
{
continue;
}
if (value is JArray arr && int.TryParse(path[j], out var idx) && idx >= 0 && idx < arr.Count)
if (value is JsonArray array && int.TryParse(path[j], out var idx) && idx >= 0 && idx < array.Count)
{
value = arr[idx];
value = array[idx];
}
else
{
@ -279,17 +276,12 @@ namespace Squidex.Domain.Apps.Core.HandleRules
}
}
if (value == null || value.Type == JTokenType.Null || value.Type == JTokenType.Undefined)
if (value == null || value.Type == JsonValueType.Null)
{
return Undefined;
}
if (value is JValue jValue)
{
return jValue.Value.ToString();
}
return value.ToString(Formatting.Indented) ?? Undefined;
return value.ToString() ?? Undefined;
}
}
}

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

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

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

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

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

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

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

@ -16,7 +16,6 @@
<ItemGroup>
<PackageReference Include="Jint" Version="2.11.58" />
<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="RefactoringEssentials" Version="5.6.0" 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.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Tags
{
@ -24,10 +24,10 @@ namespace Squidex.Domain.Apps.Core.Tags
Guard.NotNull(newData, nameof(newData));
var newValues = new HashSet<string>();
var newArrays = new List<JArray>();
var newArrays = new List<JsonArray>();
var oldValues = new HashSet<string>();
var oldArrays = new List<JArray>();
var oldArrays = new List<JsonArray>();
GetValues(schema, newValues, newArrays, newData);
@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Tags
{
if (normalized.TryGetValue(array[i].ToString(), out var result))
{
array[i] = result;
array[i] = JsonValue.Create(result);
}
}
}
@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Core.Tags
Guard.NotNull(schema, nameof(schema));
var tagsValues = new HashSet<string>();
var tagsArrays = new List<JArray>();
var tagsArrays = new List<JsonArray>();
GetValues(schema, tagsValues, tagsArrays, datas);
@ -73,14 +73,14 @@ namespace Squidex.Domain.Apps.Core.Tags
{
if (denormalized.TryGetValue(array[i].ToString(), out var result))
{
array[i] = result;
array[i] = JsonValue.Create(result);
}
}
}
}
}
private static void GetValues(Schema schema, HashSet<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)
{
@ -109,14 +109,12 @@ namespace Squidex.Domain.Apps.Core.Tags
{
foreach (var partition in fieldData)
{
if (partition.Value is JArray jArray)
if (partition.Value is JsonArray array)
{
foreach (var value in jArray)
foreach (var value in array)
{
if (value.Type == JTokenType.Object)
if (value is JsonObject nestedObject)
{
var nestedObject = (JObject)value;
if (nestedObject.TryGetValue(nestedField.Name, out var nestedValue))
{
ExtractTags(nestedValue, values, arrays);
@ -133,19 +131,19 @@ namespace Squidex.Domain.Apps.Core.Tags
}
}
private static void ExtractTags(JToken token, ISet<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.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
#pragma warning disable SA1028, IDE0004 // Code must not contain trailing whitespace
@ -22,7 +22,6 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public sealed class ContentValidator
{
private static readonly ContentFieldData DefaultFieldData = new ContentFieldData();
private static readonly JToken DefaultValue = JValue.CreateNull();
private readonly Schema schema;
private readonly PartitionResolver partitionResolver;
private readonly ValidationContext context;
@ -96,7 +95,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
var type = isLanguage ? "language" : "invariant value";
return new ObjectValidator<JToken>(fieldsValidators, isPartial, type, DefaultValue);
return new ObjectValidator<IJsonValue>(fieldsValidators, isPartial, type, JsonValue.Null);
}
}
}

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

@ -7,45 +7,80 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using NodaTime.Text;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent
{
public sealed class JsonValueConverter : IFieldVisitor<object>
{
private readonly JToken value;
private readonly IJsonValue value;
private JsonValueConverter(JToken value)
private JsonValueConverter(IJsonValue value)
{
this.value = value;
}
public static object ConvertValue(IField field, JToken json)
public static object ConvertValue(IField field, IJsonValue json)
{
return field.Accept(new JsonValueConverter(json));
}
public object Visit(IArrayField field)
{
return value.ToObject<List<JObject>>();
return ConvertToObjectList();
}
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)
{
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)
{
if (value.Type == JTokenType.String)
if (value.Type == JsonValueType.String)
{
var parseResult = InstantPattern.General.Parse(value.ToString());
@ -62,56 +97,126 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public object Visit(IField<GeolocationFieldProperties> field)
{
var geolocation = (JObject)value;
foreach (var property in geolocation.Properties())
if (value is JsonObject geolocation)
{
if (!string.Equals(property.Name, "latitude", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(property.Name, "longitude", StringComparison.OrdinalIgnoreCase))
foreach (var propertyName in geolocation.Keys)
{
if (!string.Equals(propertyName, "latitude", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(propertyName, "longitude", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidCastException("Geolocation can only have latitude and longitude property.");
}
}
var lat = (double)geolocation["latitude"];
var lon = (double)geolocation["longitude"];
if (geolocation.TryGetValue("latitude", out var latValue) && latValue is JsonScalar<double> latNumber)
{
var lat = latNumber.Value;
if (!lat.IsBetween(-90, 90))
{
throw new InvalidCastException("Latitude must be between -90 and 90.");
}
}
else
{
throw new InvalidCastException("Invalid json type, expected latitude/longitude object.");
}
if (geolocation.TryGetValue("longitude", out var lonValue) && lonValue is JsonScalar<double> lonNumber)
{
var lon = lonNumber.Value;
if (!lon.IsBetween(-180, 180))
{
throw new InvalidCastException("Longitude must be between -180 and 180.");
}
}
else
{
throw new InvalidCastException("Invalid json type, expected latitude/longitude object.");
}
return value;
}
throw new InvalidCastException("Invalid json type, expected latitude/longitude object.");
}
public object Visit(IField<JsonFieldProperties> field)
{
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.");
}
}
public object Visit(IField<StringFieldProperties> field)
return result;
}
throw new InvalidCastException("Invalid json type, expected array of guid strings.");
}
private object ConvertToStringList()
{
if (value is JsonArray array)
{
return value.ToString();
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.");
}
}
public object Visit(IField<TagsFieldProperties> field)
return result;
}
throw new InvalidCastException("Invalid json type, expected array of strings.");
}
private object ConvertToObjectList()
{
if (value is JsonArray array)
{
var result = new List<JsonObject>();
foreach (var item in array)
{
if (item is JsonObject obj)
{
return value.ToObject<List<string>>();
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.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries;
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
{
private readonly Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent;
private readonly Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset;
private readonly Guid contentId;
private readonly Guid schemaId;
private readonly CheckContents checkContent;
private readonly CheckAssets checkAsset;
private readonly ImmutableQueue<string> propertyPath;
public ImmutableQueue<string> Path
@ -24,18 +31,32 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
get { return propertyPath; }
}
public Guid ContentId
{
get { return contentId; }
}
public Guid SchemaId
{
get { return schemaId; }
}
public bool IsOptional { get; }
public ValidationContext(
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset)
: this(checkContent, checkAsset, ImmutableQueue<string>.Empty, false)
Guid contentId,
Guid schemaId,
CheckContents checkContent,
CheckAssets checkAsset)
: this(contentId, schemaId, checkContent, checkAsset, ImmutableQueue<string>.Empty, false)
{
}
private ValidationContext(
Func<IEnumerable<Guid>, Guid, Task<IReadOnlyList<Guid>>> checkContent,
Func<IEnumerable<Guid>, Task<IReadOnlyList<IAssetInfo>>> checkAsset,
Guid contentId,
Guid schemaId,
CheckContents checkContent,
CheckAssets checkAsset,
ImmutableQueue<string> propertyPath,
bool isOptional)
{
@ -46,23 +67,26 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
this.checkContent = checkContent;
this.checkAsset = checkAsset;
this.contentId = contentId;
this.schemaId = schemaId;
IsOptional = isOptional;
}
public ValidationContext Optional(bool isOptional)
{
return isOptional == IsOptional ? this : new ValidationContext(checkContent, checkAsset, propertyPath, isOptional);
return isOptional == IsOptional ? this : new ValidationContext(contentId, schemaId, checkContent, checkAsset, propertyPath, isOptional);
}
public ValidationContext Nested(string property)
{
return new ValidationContext(checkContent, checkAsset, propertyPath.Enqueue(property), IsOptional);
return new ValidationContext(contentId, schemaId, checkContent, checkAsset, propertyPath.Enqueue(property), IsOptional);
}
public Task<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)

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

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure;
@ -14,9 +15,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
public sealed class AllowedValuesValidator<T> : IValidator
{
private readonly T[] allowedValues;
private readonly IEnumerable<T> allowedValues;
public AllowedValuesValidator(params T[] allowedValues)
: this((IEnumerable<T>)allowedValues)
{
}
public AllowedValuesValidator(IEnumerable<T> 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.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
@ -30,9 +29,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
object typedValue = null;
if (value is JToken jToken)
if (value is IJsonValue jsonValue)
{
typedValue = jToken.IsNull() ? null : JsonValueConverter.ConvertValue(field, jToken);
typedValue = jsonValue.Type == JsonValueType.Null ? null : JsonValueConverter.ConvertValue(field, jsonValue);
}
var tasks = new List<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)
{
if (value is IDictionary<string, TValue> values)
if (value is IReadOnlyDictionary<string, TValue> 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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
public sealed class ReferencesValidator : IValidator
{
private static readonly IReadOnlyList<string> Path = new List<string> { "Id" };
private readonly Guid schemaId;
public ReferencesValidator(Guid schemaId)
@ -24,11 +28,16 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
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.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ValidateContent
{
@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
nestedSchema[nestedField.Name] = (false, new FieldValidator(nestedField.Accept(this).ToArray(), nestedField));
}
yield return new CollectionItemValidator(new ObjectValidator<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)
@ -109,7 +109,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
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)
{
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);
}
if (field.Properties.AllowedValues != null)
{
yield return new CollectionItemValidator(new AllowedValuesValidator<string>(field.Properties.AllowedValues));
}
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.ExtractReferenceIds;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
@ -22,24 +23,24 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return data.GetReferencedIds(schema).ToList();
}
public static NamedContentData FromMongoModel(this IdContentData result, Schema schema, List<Guid> deletedIds)
public static NamedContentData FromMongoModel(this IdContentData result, Schema schema, List<Guid> deletedIds, IJsonSerializer serializer)
{
return result.ConvertId2Name(schema,
FieldConverters.ForValues(
ValueConverters.DecodeJson(),
ValueConverters.DecodeJson(serializer),
ValueReferencesConverter.CleanReferences(deletedIds)),
FieldConverters.ForNestedId2Name(
ValueConverters.DecodeJson(),
ValueConverters.DecodeJson(serializer),
ValueReferencesConverter.CleanReferences(deletedIds)));
}
public static IdContentData ToMongoModel(this NamedContentData result, Schema schema)
public static IdContentData ToMongoModel(this NamedContentData result, Schema schema, IJsonSerializer serializer)
{
return result.ConvertName2Id(schema,
FieldConverters.ForValues(
ValueConverters.EncodeJson()),
ValueConverters.EncodeJson(serializer)),
FieldConverters.ForNestedName2Id(
ValueConverters.EncodeJson()));
ValueConverters.EncodeJson(serializer)));
}
}
}

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.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
@ -26,10 +27,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
private readonly string collectionName;
public MongoContentCollection(IMongoDatabase database, string collectionName)
protected IJsonSerializer Serializer { get; }
public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer, string collectionName)
: base(database)
{
this.collectionName = collectionName;
Serializer = serializer;
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default(CancellationToken))
@ -49,7 +54,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
query = query.AdjustToModel(schema.SchemaDef, useDraft);
var filter = FindExtensions.BuildQuery(query, schema.Id, status);
var filter = query.ToFilter(schema.Id, status);
var contentCount = Collection.Find(filter).CountDocumentsAsync();
var contentItems =
@ -64,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
foreach (var entity in contentItems.Result)
{
entity.ParseData(schema.SchemaDef);
entity.ParseData(schema.SchemaDef, Serializer);
}
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result);
@ -96,7 +101,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
foreach (var entity in contentItems.Result)
{
entity.ParseData(schema.SchemaDef);
entity.ParseData(schema.SchemaDef, Serializer);
}
return ResultList.Create<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.Contents;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
@ -26,8 +29,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
internal sealed class MongoContentDraftCollection : MongoContentCollection
{
public MongoContentDraftCollection(IMongoDatabase database)
: base(database, "State_Content_Draft")
public MongoContentDraftCollection(IMongoDatabase database, IJsonSerializer serializer)
: base(database, serializer, "State_Content_Draft")
{
}
@ -52,13 +55,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
await base.SetupCollectionAsync(collection, ct);
}
public async Task<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 =
await Collection.Find(x => x.IndexedSchemaId == schemaId && ids.Contains(x.Id) && x.IsDeleted != true).Only(x => x.Id)
await Collection.Find(filter).Only(x => x.Id)
.ToListAsync();
return ids.Except(contentEntities.Select(x => Guid.Parse(x["_id"].AsString))).ToList();
return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
}
public async Task<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)
.FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef);
contentEntity?.ParseData(schema.SchemaDef, Serializer);
return contentEntity;
}
@ -103,7 +108,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId);
contentEntity.ParseData(schema.SchemaDef);
contentEntity.ParseData(schema.SchemaDef, Serializer);
return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
}

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

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

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.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Queries;
@ -26,17 +27,21 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
private readonly IMongoDatabase database;
private readonly IAppProvider appProvider;
private readonly IJsonSerializer serializer;
private readonly MongoContentDraftCollection contentsDraft;
private readonly MongoContentPublishedCollection contentsPublished;
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider)
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, IJsonSerializer serializer)
{
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(serializer, nameof(serializer));
this.appProvider = appProvider;
contentsDraft = new MongoContentDraftCollection(database);
contentsPublished = new MongoContentPublishedCollection(database);
this.serializer = serializer;
contentsDraft = new MongoContentDraftCollection(database, serializer);
contentsPublished = new MongoContentPublishedCollection(database, serializer);
this.database = database;
}
@ -91,11 +96,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
}
}
public async Task<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>())
{
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 idData = value.Data.ToMongoModel(schema.SchemaDef);
var idData = value.Data.ToMongoModel(schema.SchemaDef, serializer);
var idDraftData = idData;
if (!ReferenceEquals(value.Data, value.DataDraft))
{
idDraftData = value.DataDraft?.ToMongoModel(schema.SchemaDef);
idDraftData = value.DataDraft?.ToMongoModel(schema.SchemaDef, serializer);
}
var content = SimpleMapper.Map(value, new MongoContentEntity

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

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

@ -11,7 +11,6 @@ using System.Linq;
using System.Reflection;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Domain.Apps.Core.Schemas;
@ -28,33 +27,30 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
typeof(MongoContentEntity).GetProperties()
.ToDictionary(x => x.Name, x => x.GetCustomAttribute<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)
{
var value = nodeIn.Rhs.Value;
query.Sort = query.Sort.Select(x => new SortNode(pathConverter(x.Path), x.SortOrder)).ToList();
if (value is Instant &&
!string.Equals(nodeIn.Lhs[0], "mt", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(nodeIn.Lhs[0], "ct", StringComparison.OrdinalIgnoreCase))
{
return new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, new FilterValue(value.ToString()));
return query;
}
return new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, nodeIn.Rhs);
}
public static FilterNode AdjustToModel(this FilterNode filterNode, Schema schema, bool useDraft)
{
var pathConverter = PathConverter(schema, useDraft);
return filterNode.Accept(new AdaptionVisitor(pathConverter));
}
public static Query AdjustToModel(this Query query, Schema schema, bool useDraft)
private static Func<IReadOnlyList<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);
@ -96,15 +92,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
return result;
});
if (query.Filter != null)
{
query.Filter = query.Filter.Accept(new AdaptionVisitor(pathConverter));
}
query.Sort = query.Sort.Select(x => new SortNode(pathConverter(x.Path), x.SortOrder)).ToList();
return query;
}
public static IFindFluent<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);
}
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>>
{
Filter.Eq(x => x.IndexedSchemaId, schemaId)
Filter.Eq(x => x.IndexedSchemaId, schemaId),
Filter.Ne(x => x.IsDeleted, true)
};
if (status != null)
{
filters.Add(Filter.Ne(x => x.IsDeleted, true));
filters.Add(Filter.In(x => x.Status, status));
}
@ -149,14 +136,19 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
}
}
if (filters.Count == 1)
{
return filters[0];
return Filter.And(filters);
}
else
public static FilterDefinition<MongoContentEntity> ToFilter(this FilterNode filterNode, Guid schemaId)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
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.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
[CollectionName("UISettings")]
public sealed class State
{
public JObject Settings { get; set; } = new JObject();
public JsonObject Settings { get; set; } = JsonValue.Object();
}
public AppUISettingsGrain(IStore<Guid> store)
@ -41,19 +41,19 @@ namespace Squidex.Domain.Apps.Entities.Apps
return persistence.ReadAsync();
}
public Task<J<JObject>> GetAsync()
public Task<J<JsonObject>> GetAsync()
{
return Task.FromResult(state.Settings.AsJ());
}
public Task SetAsync(J<JObject> settings)
public Task SetAsync(J<JsonObject> settings)
{
state.Settings = settings;
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);
@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
throw new InvalidOperationException("Path does not lead to an object.");
}
container[key] = value;
container[key] = value.Value;
return persistence.WriteSnapshotAsync(state);
}
@ -79,7 +79,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
return persistence.WriteSnapshotAsync(state);
}
private JObject GetContainer(string path, out string key)
private JsonObject GetContainer(string path, out string key)
{
Guard.NotNullOrEmpty(path, nameof(path));
@ -95,12 +95,12 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
if (!current.TryGetValue(segment, out var temp))
{
temp = new JObject();
temp = JsonValue.Object();
current[segment] = temp;
}
if (temp is JObject next)
if (temp is JsonObject next)
{
current = next;
}

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

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

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

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

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

@ -6,19 +6,19 @@
// ==========================================================================
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Orleans;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps
{
public interface IAppUISettingsGrain : IGrainWithGuidKey
{
Task<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);
}

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

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

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

@ -5,9 +5,9 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Immutable;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Schemas.Commands;
using Squidex.Infrastructure.Collections;
namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
{
@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates.Builders
public StringFieldBuilder AsDropDown(params string[] values)
{
Properties<StringFieldProperties>().AllowedValues = ImmutableList.Create(values);
Properties<StringFieldProperties>().AllowedValues = ReadOnlyCollection.Create(values);
Properties<StringFieldProperties>().Editor = StringFieldEditor.Dropdown;
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)
{
var entries = await usageStore.QueryAsync(appId.ToString(), SummaryDate, SummaryDate);
var key = GetKey(appId);
var entries = await usageStore.QueryAsync(key, SummaryDate, SummaryDate);
return (long)entries.Select(x => x.Counters.Get(CounterTotalSize)).FirstOrDefault();
}
@ -43,7 +45,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
var enriched = new List<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))
{

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

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

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

@ -8,7 +8,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Entities.Assets.State;
@ -89,16 +88,16 @@ namespace Squidex.Domain.Apps.Entities.Assets
private async Task RestoreTagsAsync(Guid appId, BackupReader reader)
{
var tags = await reader.ReadJsonAttachmentAsync(TagsFile);
var tags = await reader.ReadJsonAttachmentAsync<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)
{
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)

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

@ -7,7 +7,7 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Runtime.Serialization;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets;
@ -20,37 +20,37 @@ namespace Squidex.Domain.Apps.Entities.Assets.State
{
public class AssetState : DomainObjectState<AssetState>, IAssetEntity
{
[JsonProperty]
[DataMember]
public NamedId<Guid> AppId { get; set; }
[JsonProperty]
[DataMember]
public string FileName { get; set; }
[JsonProperty]
[DataMember]
public string MimeType { get; set; }
[JsonProperty]
[DataMember]
public long FileVersion { get; set; }
[JsonProperty]
[DataMember]
public long FileSize { get; set; }
[JsonProperty]
[DataMember]
public long TotalSize { get; set; }
[JsonProperty]
[DataMember]
public bool IsImage { get; set; }
[JsonProperty]
[DataMember]
public int? PixelWidth { get; set; }
[JsonProperty]
[DataMember]
public int? PixelHeight { get; set; }
[JsonProperty]
[DataMember]
public bool IsDeleted { get; set; }
[JsonProperty]
[DataMember]
public HashSet<string> Tags { get; set; }
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.Assets;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.States;
@ -34,6 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
private readonly IBackupArchiveLocation backupArchiveLocation;
private readonly IClock clock;
private readonly IEnumerable<BackupHandler> handlers;
private readonly IJsonSerializer serializer;
private readonly IEventDataFormatter eventDataFormatter;
private readonly IEventStore eventStore;
private readonly ISemanticLog log;
@ -51,6 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
IEventStore eventStore,
IEventDataFormatter eventDataFormatter,
IEnumerable<BackupHandler> handlers,
IJsonSerializer serializer,
ISemanticLog log,
IStore<Guid> store)
{
@ -60,6 +63,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(eventDataFormatter, nameof(eventDataFormatter));
Guard.NotNull(handlers, nameof(handlers));
Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(store, nameof(store));
Guard.NotNull(log, nameof(log));
@ -69,6 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
this.eventStore = eventStore;
this.eventDataFormatter = eventDataFormatter;
this.handlers = handlers;
this.serializer = serializer;
this.store = store;
this.log = log;
}
@ -139,7 +144,7 @@ namespace Squidex.Domain.Apps.Entities.Backup
{
using (var stream = await backupArchiveLocation.OpenStreamAsync(job.Id))
{
using (var writer = new BackupWriter(stream, true))
using (var writer = new BackupWriter(serializer, stream, true))
{
await eventStore.QueryAsync(async storedEvent =>
{

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

Loading…
Cancel
Save