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/Status2.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/Status2.cs
new file mode 100644
index 000000000..3e684bbe7
--- /dev/null
+++ b/src/Squidex.Domain.Apps.Core.Model/Contents/Status2.cs
@@ -0,0 +1,46 @@
+// ==========================================================================
+// Squidex Headless CMS
+// ==========================================================================
+// Copyright (c) Squidex UG (haftungsbeschränkt)
+// All rights reserved. Licensed under the MIT license.
+// ==========================================================================
+
+using Squidex.Infrastructure;
+using System;
+
+namespace Squidex.Domain.Apps.Core.Contents
+{
+ public struct Status2 : IEquatable
+ {
+ public static readonly Status2 Published = new Status2("Published");
+
+ public string Name { get; }
+
+ public Status2(string name)
+ {
+ Guard.NotNullOrEmpty(name, nameof(name));
+
+ Name = name;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is Status2 status && Equals(status);
+ }
+
+ public bool Equals(Status2 other)
+ {
+ return Name.Equals(other.Name);
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+}
diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/StatusFlow.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/StatusFlow.cs
index 005b2d4b3..7add93c24 100644
--- a/src/Squidex.Domain.Apps.Core.Model/Contents/StatusFlow.cs
+++ b/src/Squidex.Domain.Apps.Core.Model/Contents/StatusFlow.cs
@@ -28,5 +28,10 @@ namespace Squidex.Domain.Apps.Core.Contents
{
return Flow.TryGetValue(status, out var state) && state.Contains(toStatus);
}
+
+ public static IEnumerable Next(Status status)
+ {
+ return Flow.TryGetValue(status, out var result) ? result : Enumerable.Empty();
+ }
}
}
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.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..0b9bf91f0
--- /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 Status2 Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
+ {
+ var value = context.Reader.ReadString();
+
+ return new Status2(value);
+ }
+
+ public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Status2 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/AppGrain.cs b/src/Squidex.Domain.Apps.Entities/Apps/AppGrain.cs
index 63ac53ab9..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:
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/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