diff --git a/.testrunsettings b/.testrunsettings
new file mode 100644
index 000000000..0082141a9
--- /dev/null
+++ b/.testrunsettings
@@ -0,0 +1,6 @@
+
+
+
+ 4
+
+
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7941618aa..9037de7bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,33 @@
# Changelog
+## v2.1.0 - 2019-06-22
+
+## Features
+
+* **Assets**: Parameter to prevent download in Browser.
+* **Assets**: FTP asset store.
+* **GraphQL**: Logging for field resolvers
+* **GraphQL**: Performance optimizations for asset fields and references with DataLoader.
+* **MongoDB**: Performance optimizations.
+* **MongoDB**: Support for AWS DocumentDB.
+* **Schemas**: Separator field.
+* **Schemas**: Setting to prevent duplicate references.
+* **UI**: Improved styling of DateTime editor.
+* **UI**: Custom Editors: Provide all values.
+* **UI**: Custom Editors: Provide context with user information and auth token.
+* **UI**: Filter by status.
+* **UI**: Dropdown field for references.
+* **Users**: Email notifications when contributors is added.
+
+## Bugfixes
+
+* **Contents**: Fix for scheduled publishing.
+* **GraphQL**: Fix query parameters for assets.
+* **GraphQL**: Fix for duplicate field names in GraphQL.
+* **GraphQL**: Fix for invalid field names.
+* **Plans**: Fix when plans reset and extra events.
+* **UI**: Unify slugify in Frontend and Backend.
+
## v2.0.5 - 2019-04-21
## Features
diff --git a/Dockerfile b/Dockerfile
index 86485f677..e57b35d67 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,11 +5,20 @@ FROM squidex/dotnet:2.2-sdk-chromium-phantomjs-node as builder
WORKDIR /src
+# Copy Node project files.
COPY src/Squidex/package*.json /tmp/
# Install Node packages
RUN cd /tmp && npm install --loglevel=error
+# Copy nuget project files.
+COPY /**/**/*.csproj /tmp/
+# Copy nuget.config for package sources.
+COPY NuGet.Config /tmp/
+
+# Install nuget packages
+RUN bash -c 'pushd /tmp; for p in *.csproj; do dotnet restore $p --verbosity quiet; true; done; popd'
+
COPY . .
# Build Frontend
@@ -19,8 +28,7 @@ RUN cp -a /tmp/node_modules src/Squidex/ \
&& npm run build
# Test Backend
-RUN dotnet restore \
- && dotnet test --filter Category!=Dependencies tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj \
+RUN dotnet test tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj --filter Category!=Dependencies \
&& dotnet test tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj \
&& dotnet test tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj \
&& dotnet test tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj \
diff --git a/Dockerfile.build b/Dockerfile.build
index 30a5d3091..96debc8cd 100644
--- a/Dockerfile.build
+++ b/Dockerfile.build
@@ -2,11 +2,20 @@ FROM squidex/dotnet:2.2-sdk-chromium-phantomjs-node as builder
WORKDIR /src
+# Copy Node project files.
COPY src/Squidex/package*.json /tmp/
# Install Node packages
RUN cd /tmp && npm install --loglevel=error
+# Copy Dotnet project files.
+COPY /**/**/*.csproj /tmp/
+# Copy nuget.config for package sources.
+COPY NuGet.Config /tmp/
+
+# Install Dotnet packages
+RUN bash -c 'pushd /tmp; for p in *.csproj; do dotnet restore $p --verbosity quiet; true; done; popd'
+
COPY . .
# Build Frontend
@@ -16,8 +25,7 @@ RUN cp -a /tmp/node_modules src/Squidex/ \
&& npm run build
# Test Backend
-RUN dotnet restore \
- && dotnet test --filter Category!=Dependencies tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj \
+RUN dotnet test tests/Squidex.Infrastructure.Tests/Squidex.Infrastructure.Tests.csproj --filter Category!=Dependencies \
&& dotnet test tests/Squidex.Domain.Apps.Core.Tests/Squidex.Domain.Apps.Core.Tests.csproj \
&& dotnet test tests/Squidex.Domain.Apps.Entities.Tests/Squidex.Domain.Apps.Entities.Tests.csproj \
&& dotnet test tests/Squidex.Domain.Users.Tests/Squidex.Domain.Users.Tests.csproj \
diff --git a/README.md b/README.md
index 2484db7f7..bbf5dfe3b 100644
--- a/README.md
+++ b/README.md
@@ -34,9 +34,10 @@ Current Version v2.0.4. Roadmap: https://trello.com/b/KakM4F3S/squidex-roadmap
### Contributors
-* [pushrbx](https://pushrbx.net/): Azure Store support.
-* [cpmstars](https://www.cpmstars.com): Asset support for rich editor.
* [civicplus](https://www.civicplus.com/) ([Avd6977](https://github.com/Avd6977), [dsbegnoce](https://github.com/dsbegnoche)): Google Maps support, custom regex patterns and a lot of small improvements.
+* [cpmstars](https://www.cpmstars.com): Asset support for rich editor.
+* [guohai](https://github.com/seamys): FTP asset store support, Email rule support, custom editors and bug fixes.
+* [pushrbx](https://pushrbx.net/): Azure Store support.
* [razims](https://github.com/razims): GridFS support.
## Contributing
diff --git a/Squidex.ruleset b/Squidex.ruleset
index 055510070..5aae5da01 100644
--- a/Squidex.ruleset
+++ b/Squidex.ruleset
@@ -63,6 +63,7 @@
+
diff --git a/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs b/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs
index 700fb4246..0e2770682 100644
--- a/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs
+++ b/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs
@@ -7,6 +7,7 @@
using Squidex.Infrastructure;
using Squidex.Infrastructure.Security;
+using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using P = Squidex.Shared.Permissions;
@@ -20,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Apps
public const string Owner = "Owner";
public const string Reader = "Reader";
- private static readonly HashSet DefaultRolesSet = new HashSet
+ private static readonly HashSet DefaultRolesSet = new HashSet(StringComparer.OrdinalIgnoreCase)
{
Editor,
Developer,
@@ -54,6 +55,11 @@ namespace Squidex.Domain.Apps.Core.Apps
return role != null && DefaultRolesSet.Contains(role);
}
+ public static bool IsRole(string name, string expected)
+ {
+ return name != null && string.Equals(name, expected, StringComparison.OrdinalIgnoreCase);
+ }
+
public static Role CreateOwner(string app)
{
return new Role(Owner,
diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/Json/StatusConverter.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/Json/StatusConverter.cs
new file mode 100644
index 000000000..a56722c55
--- /dev/null
+++ b/src/Squidex.Domain.Apps.Core.Model/Contents/Json/StatusConverter.cs
@@ -0,0 +1,42 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschränkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using Newtonsoft.Json;
+using Squidex.Infrastructure.Json.Newtonsoft;
+using System;
+using System.Collections.Generic;
+
+namespace Squidex.Domain.Apps.Core.Contents.Json
+{
+ public sealed class StatusConverter : JsonConverter, ISupportedTypes
+ {
+ public IEnumerable SupportedTypes
+ {
+ get { yield return typeof(Status); }
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ writer.WriteValue(value.ToString());
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType != JsonToken.String)
+ {
+ throw new JsonException($"Expected String, but got {reader.TokenType}.");
+ }
+
+ return new Status(reader.Value.ToString());
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Status);
+ }
+ }
+}
diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs
index c20e0c4eb..d4c0374c8 100644
--- a/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs
+++ b/src/Squidex.Domain.Apps.Core.Model/Contents/Status.cs
@@ -5,12 +5,57 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
+using Squidex.Infrastructure;
+using System;
+
namespace Squidex.Domain.Apps.Core.Contents
{
- public enum Status
+ public struct Status : IEquatable
{
- Draft,
- Archived,
- Published
+ public static readonly Status Archived = new Status("Archived");
+ public static readonly Status Draft = new Status("Draft");
+ public static readonly Status Published = new Status("Published");
+
+ private readonly string name;
+
+ public string Name
+ {
+ get { return name ?? "Unknown"; }
+ }
+
+ public Status(string name)
+ {
+ this.name = name;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is Status status && Equals(status);
+ }
+
+ public bool Equals(Status other)
+ {
+ return string.Equals(name, other.name);
+ }
+
+ public override int GetHashCode()
+ {
+ return name?.GetHashCode() ?? 0;
+ }
+
+ public override string ToString()
+ {
+ return name;
+ }
+
+ public static bool operator ==(Status lhs, Status rhs)
+ {
+ return lhs.Equals(rhs);
+ }
+
+ public static bool operator !=(Status lhs, Status rhs)
+ {
+ return !lhs.Equals(rhs);
+ }
}
}
diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/StatusChange.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/StatusChange.cs
index 9e3900deb..eae462221 100644
--- a/src/Squidex.Domain.Apps.Core.Model/Contents/StatusChange.cs
+++ b/src/Squidex.Domain.Apps.Core.Model/Contents/StatusChange.cs
@@ -1,7 +1,7 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschraenkt)
+// Copyright (c) Squidex UG (haftungsbeschränkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
@@ -9,9 +9,8 @@ namespace Squidex.Domain.Apps.Core.Contents
{
public enum StatusChange
{
- Archived,
+ Change,
Published,
- Restored,
Unpublished
}
}
diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/StatusFlow.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/StatusFlow.cs
deleted file mode 100644
index 005b2d4b3..000000000
--- a/src/Squidex.Domain.Apps.Core.Model/Contents/StatusFlow.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// ==========================================================================
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschränkt)
-// All rights reserved. Licensed under the MIT license.
-// ==========================================================================
-
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Squidex.Domain.Apps.Core.Contents
-{
- public static class StatusFlow
- {
- private static readonly Dictionary Flow = new Dictionary
- {
- [Status.Draft] = new[] { Status.Published, Status.Archived },
- [Status.Archived] = new[] { Status.Draft },
- [Status.Published] = new[] { Status.Draft, Status.Archived }
- };
-
- public static bool Exists(Status status)
- {
- return Flow.ContainsKey(status);
- }
-
- public static bool CanChange(Status status, Status toStatus)
- {
- return Flow.TryGetValue(status, out var state) && state.Contains(toStatus);
- }
- }
-}
diff --git a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs
index d09841b55..f07114303 100644
--- a/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs
+++ b/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs
@@ -122,9 +122,14 @@ namespace Squidex.Domain.Apps.Core.Schemas
{
Guard.NotNull(field, nameof(field));
- if (ByName.ContainsKey(field.Name) || ById.ContainsKey(field.Id))
+ if (ByName.ContainsKey(field.Name))
{
- throw new ArgumentException($"A field with name '{field.Name}' and id {field.Id} already exists.", nameof(field));
+ throw new ArgumentException($"A field with name '{field.Name}' already exists.", nameof(field));
+ }
+
+ if (ById.ContainsKey(field.Id))
+ {
+ throw new ArgumentException($"A field with id {field.Id} already exists.", nameof(field));
}
return Clone(clone =>
diff --git a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs
index 45148a8e2..565272ef6 100644
--- a/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs
+++ b/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EnrichedEvents/EnrichedContentEventType.cs
@@ -9,12 +9,11 @@ namespace Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents
{
public enum EnrichedContentEventType
{
- Archived,
Created,
Deleted,
Published,
- Restored,
+ StatusChanged,
+ Updated,
Unpublished,
- Updated
}
}
diff --git a/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs b/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs
index 3d2177d4b..d32a234bd 100644
--- a/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs
+++ b/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintUser.cs
@@ -48,9 +48,9 @@ namespace Squidex.Domain.Apps.Core.Scripting
private static ObjectWrapper CreateUser(Engine engine, string id, bool isClient, string email, string name, IEnumerable allClaims)
{
var claims =
- allClaims.GroupBy(x => x.Type)
+ allClaims.GroupBy(x => x.Type.Split(ClaimSeparators).Last())
.ToDictionary(
- x => x.Key.Split(ClaimSeparators).Last(),
+ x => x.Key,
x => x.Select(y => y.Value).ToArray());
return new ObjectWrapper(engine, new { id, isClient, email, name, claims });
diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs
index eafa95d22..8f2f2689c 100644
--- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs
+++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs
@@ -34,11 +34,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
var typedValue = value;
- if (value == null)
- {
- typedValue = Undefined.Value;
- }
- else if (value is IJsonValue jsonValue)
+ if (value is IJsonValue jsonValue)
{
if (jsonValue.Type == JsonValueType.Null)
{
diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs
index 6c2b26246..c86c85be3 100644
--- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs
+++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs
@@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
public async Task ValidateAsync(object value, ValidationContext context, AddError addError)
{
- if (value == null)
+ if (value.IsNullOrUndefined())
{
value = DefaultValue;
}
@@ -49,17 +49,22 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{
var name = field.Key;
- if (!values.TryGetValue(name, out var fieldValue))
+ var (isOptional, validator) = field.Value;
+
+ var fieldValue = Undefined.Value;
+
+ if (!values.TryGetValue(name, out var temp))
{
if (isPartial)
{
continue;
}
-
- fieldValue = default;
+ }
+ else
+ {
+ fieldValue = temp;
}
- var (isOptional, validator) = field.Value;
var fieldContext = context.Nested(name).Optional(isOptional);
tasks.Add(validator.ValidateAsync(fieldValue, fieldContext, addError));
diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
index 44d93d8d4..6444ba638 100644
--- a/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
+++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
@@ -119,12 +119,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
var find = Collection.Find(x => ids.Contains(x.Id)).SortByDescending(x => x.LastModified);
- var assetItems = find.ToListAsync();
- var assetCount = find.CountDocumentsAsync();
+ var assetItems = await find.ToListAsync();
- await Task.WhenAll(assetItems, assetCount);
-
- return ResultList.Create(assetCount.Result, assetItems.Result.OfType());
+ return ResultList.Create(assetItems.Count, assetItems.OfType());
}
}
diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
index 1bb4c6128..9424333c0 100644
--- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
+++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
@@ -137,17 +137,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
var find = Collection.Find(FilterFactory.IdsBySchema(schema.Id, ids, status));
- var contentItems = find.WithoutDraft(includeDraft).ToListAsync();
- var contentCount = find.CountDocumentsAsync();
-
- await Task.WhenAll(contentItems, contentCount);
+ var contentItems = await find.WithoutDraft(includeDraft).ToListAsync();
- foreach (var entity in contentItems.Result)
+ foreach (var entity in contentItems)
{
entity.ParseData(schema.SchemaDef, serializer);
}
- return ResultList.Create(contentCount.Result, contentItems.Result);
+ return ResultList.Create(contentItems.Count, contentItems);
}
public async Task FindContentAsync(ISchemaEntity schema, Guid id, Status[] status, bool includeDraft)
diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
index 9c8f3eba7..fe2e0649c 100644
--- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
+++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
@@ -51,7 +51,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
[BsonRequired]
[BsonElement("ss")]
- [BsonRepresentation(BsonType.String)]
public Status Status { get; set; }
[BsonIgnoreIfNull]
diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
index 5dd3020e7..aa24583e6 100644
--- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
+++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
@@ -36,6 +36,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
private readonly string typeContentDeleted;
private readonly MongoContentCollection contents;
+ static MongoContentRepository()
+ {
+ StatusSerializer.Register();
+ }
+
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, IJsonSerializer serializer, ITextIndexer indexer, TypeNameRegistry typeNameRegistry)
{
Guard.NotNull(appProvider, nameof(appProvider));
@@ -64,7 +69,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
Guard.NotNull(app, nameof(app));
Guard.NotNull(schema, nameof(schema));
- Guard.NotNull(status, nameof(status));
Guard.NotNull(query, nameof(query));
using (Profiler.TraceMethod("QueryAsyncByQuery"))
@@ -83,9 +87,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet ids, bool includeDraft = true)
{
Guard.NotNull(app, nameof(app));
- Guard.NotNull(schema, nameof(schema));
- Guard.NotNull(status, nameof(status));
Guard.NotNull(ids, nameof(ids));
+ Guard.NotNull(schema, nameof(schema));
using (Profiler.TraceMethod("QueryAsyncByIds"))
{
@@ -96,7 +99,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task> QueryAsync(IAppEntity app, Status[] status, HashSet ids, bool includeDraft = true)
{
Guard.NotNull(app, nameof(app));
- Guard.NotNull(status, nameof(status));
Guard.NotNull(ids, nameof(ids));
using (Profiler.TraceMethod("QueryAsyncByIdsWithoutSchema"))
@@ -109,7 +111,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
Guard.NotNull(app, nameof(app));
Guard.NotNull(schema, nameof(schema));
- Guard.NotNull(status, nameof(status));
using (Profiler.TraceMethod())
{
diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs
new file mode 100644
index 000000000..5d59c836a
--- /dev/null
+++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/StatusSerializer.cs
@@ -0,0 +1,39 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using System.Threading;
+using MongoDB.Bson.Serialization;
+using MongoDB.Bson.Serialization.Serializers;
+using Squidex.Domain.Apps.Core.Contents;
+
+namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
+{
+ public sealed class StatusSerializer : SerializerBase
+ {
+ private static volatile int isRegistered;
+
+ public static void Register()
+ {
+ if (Interlocked.Increment(ref isRegistered) == 1)
+ {
+ BsonSerializer.RegisterSerializer(new StatusSerializer());
+ }
+ }
+
+ public override Status Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
+ {
+ var value = context.Reader.ReadString();
+
+ return new Status(value);
+ }
+
+ public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Status value)
+ {
+ context.Writer.WriteString(value.Name);
+ }
+ }
+}
diff --git a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs
index bbed5d51f..1dd0b1500 100644
--- a/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs
+++ b/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs
@@ -162,7 +162,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
}
filters.Add(Filter.Ne(x => x.IsDeleted, true));
- filters.Add(Filter.In(x => x.Status, status));
+
+ if (status != null)
+ {
+ filters.Add(Filter.In(x => x.Status, status));
+ }
if (ids != null && ids.Count > 0)
{
diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs
new file mode 100644
index 000000000..8f94a80cf
--- /dev/null
+++ b/src/Squidex.Domain.Apps.Entities/Apps/AppExtensions.cs
@@ -0,0 +1,20 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschraenkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using System;
+using Squidex.Infrastructure;
+
+namespace Squidex.Domain.Apps.Entities.Apps
+{
+ public static class AppExtensions
+ {
+ public static NamedId NamedId(this IAppEntity app)
+ {
+ return new NamedId(app.Id, app.Name);
+ }
+ }
+}
diff --git a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
index 3645192a0..eceb7bfa5 100644
--- a/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
+++ b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
@@ -60,11 +60,13 @@ namespace Squidex.Domain.Apps.Entities.Apps
switch (command)
{
case CreateApp createApp:
- return CreateAsync(createApp, c =>
+ return CreateReturn(createApp, c =>
{
GuardApp.CanCreate(c);
Create(c);
+
+ return Snapshot;
});
case AssignContributor assignContributor:
@@ -74,111 +76,137 @@ namespace Squidex.Domain.Apps.Entities.Apps
AssignContributor(c, !Snapshot.Contributors.ContainsKey(assignContributor.ContributorId));
- return EntityCreatedResult.Create(c.ContributorId, Version);
+ return Snapshot;
});
case RemoveContributor removeContributor:
- return UpdateAsync(removeContributor, c =>
+ return UpdateReturn(removeContributor, c =>
{
GuardAppContributors.CanRemove(Snapshot.Contributors, c);
RemoveContributor(c);
+
+ return Snapshot;
});
case AttachClient attachClient:
- return UpdateAsync(attachClient, c =>
+ return UpdateReturn(attachClient, c =>
{
GuardAppClients.CanAttach(Snapshot.Clients, c);
AttachClient(c);
+
+ return Snapshot;
});
case UpdateClient updateClient:
- return UpdateAsync(updateClient, c =>
+ return UpdateReturn(updateClient, c =>
{
GuardAppClients.CanUpdate(Snapshot.Clients, c, Snapshot.Roles);
UpdateClient(c);
+
+ return Snapshot;
});
case RevokeClient revokeClient:
- return UpdateAsync(revokeClient, c =>
+ return UpdateReturn(revokeClient, c =>
{
GuardAppClients.CanRevoke(Snapshot.Clients, c);
RevokeClient(c);
+
+ return Snapshot;
});
case AddLanguage addLanguage:
- return UpdateAsync(addLanguage, c =>
+ return UpdateReturn(addLanguage, c =>
{
GuardAppLanguages.CanAdd(Snapshot.LanguagesConfig, c);
AddLanguage(c);
+
+ return Snapshot;
});
case RemoveLanguage removeLanguage:
- return UpdateAsync(removeLanguage, c =>
+ return UpdateReturn(removeLanguage, c =>
{
GuardAppLanguages.CanRemove(Snapshot.LanguagesConfig, c);
RemoveLanguage(c);
+
+ return Snapshot;
});
case UpdateLanguage updateLanguage:
- return UpdateAsync(updateLanguage, c =>
+ return UpdateReturn(updateLanguage, c =>
{
GuardAppLanguages.CanUpdate(Snapshot.LanguagesConfig, c);
UpdateLanguage(c);
+
+ return Snapshot;
});
case AddRole addRole:
- return UpdateAsync(addRole, c =>
+ return UpdateReturn(addRole, c =>
{
GuardAppRoles.CanAdd(Snapshot.Roles, c);
AddRole(c);
+
+ return Snapshot;
});
case DeleteRole deleteRole:
- return UpdateAsync(deleteRole, c =>
+ return UpdateReturn(deleteRole, c =>
{
GuardAppRoles.CanDelete(Snapshot.Roles, c, Snapshot.Contributors, Snapshot.Clients);
DeleteRole(c);
+
+ return Snapshot;
});
case UpdateRole updateRole:
- return UpdateAsync(updateRole, c =>
+ return UpdateReturn(updateRole, c =>
{
GuardAppRoles.CanUpdate(Snapshot.Roles, c);
UpdateRole(c);
+
+ return Snapshot;
});
case AddPattern addPattern:
- return UpdateAsync(addPattern, c =>
+ return UpdateReturn(addPattern, c =>
{
GuardAppPatterns.CanAdd(Snapshot.Patterns, c);
AddPattern(c);
+
+ return Snapshot;
});
case DeletePattern deletePattern:
- return UpdateAsync(deletePattern, c =>
+ return UpdateReturn(deletePattern, c =>
{
GuardAppPatterns.CanDelete(Snapshot.Patterns, c);
DeletePattern(c);
+
+ return Snapshot;
});
case UpdatePattern updatePattern:
- return UpdateAsync(updatePattern, c =>
+ return UpdateReturn(updatePattern, c =>
{
GuardAppPatterns.CanUpdate(Snapshot.Patterns, c);
UpdatePattern(c);
+
+ return Snapshot;
});
case ChangePlan changePlan:
@@ -194,7 +222,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
}
else
{
- var result = await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.Id, Snapshot.Name, c.PlanId);
+ var result = await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), c.PlanId);
switch (result)
{
@@ -213,7 +241,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
case ArchiveApp archiveApp:
return UpdateAsync(archiveApp, async c =>
{
- await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.Id, Snapshot.Name, null);
+ await appPlansBillingManager.ChangePlanAsync(c.Actor.Identifier, Snapshot.NamedId(), null);
ArchiveApp(c);
});
diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs
index 3327fc914..5e2454fab 100644
--- a/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs
+++ b/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs
@@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
await Index(GetUserId(createApp)).AddAppAsync(createApp.AppId);
break;
case AssignContributor assignContributor:
- await Index(GetUserId(context)).AddAppAsync(assignContributor.AppId);
+ await Index(GetUserId(assignContributor)).AddAppAsync(assignContributor.AppId);
break;
case RemoveContributor removeContributor:
await Index(GetUserId(removeContributor)).RemoveAppAsync(removeContributor.AppId);
@@ -57,19 +57,19 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
await next();
}
- private static string GetUserId(RemoveContributor removeContributor)
+ private static string GetUserId(CreateApp createApp)
{
- return removeContributor.ContributorId;
+ return createApp.Actor.Identifier;
}
- private static string GetUserId(CreateApp createApp)
+ private static string GetUserId(AssignContributor assignContributor)
{
- return createApp.Actor.Identifier;
+ return assignContributor.ContributorId;
}
- private static string GetUserId(CommandContext context)
+ private static string GetUserId(RemoveContributor removeContributor)
{
- return context.Result>().IdOrValue;
+ return removeContributor.ContributorId;
}
private IAppsByUserIndex Index(string id)
diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs
index 0bf99f271..7bde0a4cd 100644
--- a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs
+++ b/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs
@@ -35,9 +35,9 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
await next();
- if (assignContributor.IsCreated && context.PlainResult is EntityCreatedResult id)
+ if (assignContributor.IsCreated && context.PlainResult is IAppEntity app)
{
- context.Complete(new InvitedResult { Id = id });
+ context.Complete(new InvitedResult { App = app });
}
return;
diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitedResult.cs b/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitedResult.cs
index 695be0a4b..45c6df7b9 100644
--- a/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitedResult.cs
+++ b/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitedResult.cs
@@ -5,12 +5,10 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-using Squidex.Infrastructure.Commands;
-
namespace Squidex.Domain.Apps.Entities.Apps.Invitation
{
public sealed class InvitedResult
{
- public EntityCreatedResult Id { get; set; }
+ public IAppEntity App { get; set; }
}
}
diff --git a/src/Squidex.Domain.Apps.Entities/Apps/RoleExtensions.cs b/src/Squidex.Domain.Apps.Entities/Apps/RoleExtensions.cs
index 0e0eddad9..f6464d5dc 100644
--- a/src/Squidex.Domain.Apps.Entities/Apps/RoleExtensions.cs
+++ b/src/Squidex.Domain.Apps.Entities/Apps/RoleExtensions.cs
@@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{
return id;
}
- }));
+ }).Where(x => x != "common"));
}
}
}
diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs
index 89c6342cd..933a11ddf 100644
--- a/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs
+++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/IAppPlanBillingManager.cs
@@ -7,6 +7,7 @@
using System;
using System.Threading.Tasks;
+using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Services
{
@@ -14,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Services
{
bool HasPortal { get; }
- Task ChangePlanAsync(string userId, Guid appId, string appName, string planId);
+ Task ChangePlanAsync(string userId, NamedId appId, string planId);
Task GetPortalLinkAsync(string userId);
}
diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs b/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs
index 8e968ccc7..b8c1f46ef 100644
--- a/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs
+++ b/src/Squidex.Domain.Apps.Entities/Apps/Services/Implementations/NoopAppPlanBillingManager.cs
@@ -7,6 +7,7 @@
using System;
using System.Threading.Tasks;
+using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations
{
@@ -17,7 +18,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Services.Implementations
get { return false; }
}
- public Task ChangePlanAsync(string userId, Guid appId, string appName, string planId)
+ public Task ChangePlanAsync(string userId, NamedId appId, string planId)
{
return Task.FromResult(new PlanResetResult());
}
diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs
index 5c93059f2..e45e02645 100644
--- a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs
+++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs
@@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Orleans;
+using Squidex.Domain.Apps.Core.Tags;
using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Tags;
using Squidex.Infrastructure;
@@ -24,25 +25,28 @@ namespace Squidex.Domain.Apps.Entities.Assets
private readonly IAssetQueryService assetQuery;
private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
private readonly IEnumerable> tagGenerators;
+ private readonly ITagService tagService;
public AssetCommandMiddleware(
IGrainFactory grainFactory,
IAssetQueryService assetQuery,
IAssetStore assetStore,
IAssetThumbnailGenerator assetThumbnailGenerator,
- IEnumerable> tagGenerators)
+ IEnumerable> tagGenerators,
+ ITagService tagService)
: base(grainFactory)
{
Guard.NotNull(assetStore, nameof(assetStore));
Guard.NotNull(assetQuery, nameof(assetQuery));
Guard.NotNull(assetThumbnailGenerator, nameof(assetThumbnailGenerator));
Guard.NotNull(tagGenerators, nameof(tagGenerators));
+ Guard.NotNull(tagService, nameof(tagService));
this.assetStore = assetStore;
this.assetQuery = assetQuery;
this.assetThumbnailGenerator = assetThumbnailGenerator;
-
this.tagGenerators = tagGenerators;
+ this.tagService = tagService;
}
public override async Task HandleAsync(CommandContext context, Func next)
@@ -56,9 +60,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
createAsset.Tags = new HashSet();
}
- createAsset.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(createAsset.File.OpenRead());
-
- createAsset.FileHash = await UploadAsync(context, createAsset.File);
+ await EnrichWithImageInfosAsync(createAsset);
+ await EnrichWithHashAndUploadAsync(createAsset, context);
try
{
@@ -70,13 +73,9 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
if (IsDuplicate(createAsset, existing))
{
- result = new AssetCreatedResult(
- existing.Id,
- existing.Tags,
- existing.Version,
- existing.FileVersion,
- existing.FileHash,
- true);
+ var denormalizedTags = await tagService.DenormalizeTagsAsync(createAsset.AppId.Id, TagGroups.Assets, existing.Tags);
+
+ result = new AssetCreatedResult(existing, true, new HashSet(denormalizedTags.Values));
}
break;
@@ -89,17 +88,11 @@ namespace Squidex.Domain.Apps.Entities.Assets
tagGenerator.GenerateTags(createAsset, createAsset.Tags);
}
- var commandResult = (AssetSavedResult)await ExecuteCommandAsync(createAsset);
+ var asset = (IAssetEntity)await ExecuteCommandAsync(createAsset);
- result = new AssetCreatedResult(
- createAsset.AssetId,
- createAsset.Tags,
- commandResult.Version,
- commandResult.FileVersion,
- commandResult.FileHash,
- false);
+ result = new AssetCreatedResult(asset, false, createAsset.Tags);
- await assetStore.CopyAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), result.FileVersion, null);
+ await assetStore.CopyAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), asset.FileVersion, null);
}
context.Complete(result);
@@ -114,16 +107,16 @@ namespace Squidex.Domain.Apps.Entities.Assets
case UpdateAsset updateAsset:
{
- updateAsset.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(updateAsset.File.OpenRead());
+ await EnrichWithImageInfosAsync(updateAsset);
+ await EnrichWithHashAndUploadAsync(updateAsset, context);
- updateAsset.FileHash = await UploadAsync(context, updateAsset.File);
try
{
- var result = (AssetSavedResult)await ExecuteCommandAsync(updateAsset);
+ var result = (AssetResult)await ExecuteAndAdjustTagsAsync(updateAsset);
context.Complete(result);
- await assetStore.CopyAsync(context.ContextId.ToString(), updateAsset.AssetId.ToString(), result.FileVersion, null);
+ await assetStore.CopyAsync(context.ContextId.ToString(), updateAsset.AssetId.ToString(), result.Asset.FileVersion, null);
}
finally
{
@@ -133,29 +126,54 @@ namespace Squidex.Domain.Apps.Entities.Assets
break;
}
+ case AssetCommand command:
+ {
+ var result = await ExecuteAndAdjustTagsAsync(command);
+
+ context.Complete(result);
+
+ break;
+ }
+
default:
await base.HandleAsync(context, next);
+
break;
}
}
+ private async Task