diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCommandMiddleware.cs index b13c99f29..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,7 +73,9 @@ namespace Squidex.Domain.Apps.Entities.Assets { if (IsDuplicate(createAsset, existing)) { - result = new AssetCreatedResult(existing, true); + var denormalizedTags = await tagService.DenormalizeTagsAsync(createAsset.AppId.Id, TagGroups.Assets, existing.Tags); + + result = new AssetCreatedResult(existing, true, new HashSet(denormalizedTags.Values)); } break; @@ -85,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Assets var asset = (IAssetEntity)await ExecuteCommandAsync(createAsset); - result = new AssetCreatedResult(asset, false); + result = new AssetCreatedResult(asset, false, createAsset.Tags); await assetStore.CopyAsync(context.ContextId.ToString(), createAsset.AssetId.ToString(), asset.FileVersion, null); } @@ -102,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 = (IAssetEntity)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 { @@ -121,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 ExecuteAndAdjustTagsAsync(AssetCommand command) + { + var result = await ExecuteCommandAsync(command); + + if (result is IAssetEntity asset) + { + var denormalizedTags = await tagService.DenormalizeTagsAsync(asset.AppId.Id, TagGroups.Assets, asset.Tags); + + return new AssetResult(asset, new HashSet(denormalizedTags.Values)); + } + + return result; + } + private static bool IsDuplicate(CreateAsset createAsset, IAssetEntity asset) { return asset != null && asset.FileName == createAsset.File.FileName && asset.FileSize == createAsset.File.FileSize; } - private async Task UploadAsync(CommandContext context, AssetFile file) + private async Task EnrichWithImageInfosAsync(UploadAssetCommand command) { - string hash; + command.ImageInfo = await assetThumbnailGenerator.GetImageInfoAsync(command.File.OpenRead()); + } - using (var hashStream = new HasherStream(file.OpenRead(), HashAlgorithmName.SHA256)) + private async Task EnrichWithHashAndUploadAsync(UploadAssetCommand command, CommandContext context) + { + using (var hashStream = new HasherStream(command.File.OpenRead(), HashAlgorithmName.SHA256)) { await assetStore.UploadAsync(context.ContextId.ToString(), hashStream); - hash = $"{hashStream.GetHashStringAndReset()}{file.FileName}{file.FileSize}".Sha256Base64(); + command.FileHash = $"{hashStream.GetHashStringAndReset()}{command.File.FileName}{command.File.FileSize}".Sha256Base64(); } - - return hash; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs index b1502786a..9ccc00763 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetCreatedResult.cs @@ -5,18 +5,17 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; + namespace Squidex.Domain.Apps.Entities.Assets { - public sealed class AssetCreatedResult + public sealed class AssetCreatedResult : AssetResult { - public IAssetEntity Asset { get; } - public bool IsDuplicate { get; } - public AssetCreatedResult(IAssetEntity asset, bool isDuplicate) + public AssetCreatedResult(IAssetEntity asset, bool isDuplicate, HashSet tags) + : base(asset, tags) { - Asset = asset; - IsDuplicate = isDuplicate; } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/AssetResult.cs b/src/Squidex.Domain.Apps.Entities/Assets/AssetResult.cs new file mode 100644 index 000000000..b43713da5 --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Assets/AssetResult.cs @@ -0,0 +1,25 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Collections.Generic; + +namespace Squidex.Domain.Apps.Entities.Assets +{ + public class AssetResult + { + public IAssetEntity Asset { get; } + + public HashSet Tags { get; } + + public AssetResult(IAssetEntity asset, HashSet tags) + { + Asset = asset; + + Tags = tags; + } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs index 9c49e67bd..8e869ba40 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/CreateAsset.cs @@ -8,22 +8,15 @@ using System; using System.Collections.Generic; using Squidex.Infrastructure; -using Squidex.Infrastructure.Assets; namespace Squidex.Domain.Apps.Entities.Assets.Commands { - public sealed class CreateAsset : AssetCommand, IAppCommand + public sealed class CreateAsset : UploadAssetCommand, IAppCommand { public NamedId AppId { get; set; } - public AssetFile File { get; set; } - - public ImageInfo ImageInfo { get; set; } - public HashSet Tags { get; set; } - public string FileHash { get; set; } - public CreateAsset() { AssetId = Guid.NewGuid(); diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs index 1c998ac7a..16197164d 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UpdateAsset.cs @@ -5,16 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure.Assets; - namespace Squidex.Domain.Apps.Entities.Assets.Commands { - public sealed class UpdateAsset : AssetCommand + public sealed class UpdateAsset : UploadAssetCommand { - public AssetFile File { get; set; } - - public ImageInfo ImageInfo { get; set; } - - public string FileHash { get; set; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Commands/UploadAssetCommand.cs b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UploadAssetCommand.cs new file mode 100644 index 000000000..5ef0652cd --- /dev/null +++ b/src/Squidex.Domain.Apps.Entities/Assets/Commands/UploadAssetCommand.cs @@ -0,0 +1,20 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Infrastructure.Assets; + +namespace Squidex.Domain.Apps.Entities.Assets.Commands +{ + public abstract class UploadAssetCommand : AssetCommand + { + public AssetFile File { get; set; } + + public ImageInfo ImageInfo { get; set; } + + public string FileHash { get; set; } + } +} diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs index 93b152d8c..92ccf8142 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs @@ -121,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards throw new DomainException("Schema field is already disabled."); } - if (!field.IsForApi()) + if (!field.IsForApi(true)) { throw new DomainException("UI field cannot be disabled."); } diff --git a/src/Squidex.Infrastructure/CollectionExtensions.cs b/src/Squidex.Infrastructure/CollectionExtensions.cs index 76be0fd06..cfe546a24 100644 --- a/src/Squidex.Infrastructure/CollectionExtensions.cs +++ b/src/Squidex.Infrastructure/CollectionExtensions.cs @@ -13,6 +13,14 @@ namespace Squidex.Infrastructure { public static class CollectionExtensions { + public static void AddRange(this ICollection target, IEnumerable source) + { + foreach (var value in source) + { + target.Add(value); + } + } + public static IEnumerable Shuffle(this IEnumerable enumerable) { var random = new Random(); diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 1db6f0659..965056fbe 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -179,7 +179,10 @@ namespace Squidex.Areas.Api.Controllers.Assets var command = new CreateAsset { File = assetFile }; - var response = await InvokeCommandAsync(app, command); + var context = await CommandBus.PublishAsync(command); + + var result = context.Result(); + var response = AssetDto.FromAsset(result.Asset, this, app, result.Tags, result.IsDuplicate); return CreatedAtAction(nameof(GetAsset), new { app, id = response.Id }, response); } @@ -264,8 +267,8 @@ namespace Squidex.Areas.Api.Controllers.Assets { var context = await CommandBus.PublishAsync(command); - var result = context.Result(); - var response = AssetDto.FromAsset(result, this, app); + var result = context.Result(); + var response = AssetDto.FromAsset(result.Asset, this, app, result.Tags); return response; } diff --git a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs index f44c9d25a..ec0e68899 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/Models/AssetDto.cs @@ -118,10 +118,15 @@ namespace Squidex.Areas.Api.Controllers.Assets.Models [JsonProperty("_meta")] public AssetMetadata Metadata { get; set; } - public static AssetDto FromAsset(IAssetEntity asset, ApiController controller, string app, bool isDuplicate = false) + public static AssetDto FromAsset(IAssetEntity asset, ApiController controller, string app, HashSet tags = null, bool isDuplicate = false) { var response = SimpleMapper.Map(asset, new AssetDto { FileType = asset.FileName.FileType() }); + if (tags != null) + { + response.Tags = tags; + } + if (isDuplicate) { response.Metadata = new AssetMetadata { IsDuplicate = "true" }; diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs index 3939cfd67..b2db0db33 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs @@ -142,7 +142,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models if (controller.HasPermission(Permissions.AppContentsDelete, app, schema)) { - AddPutLink("delete", controller.Url(x => nameof(x.DeleteContent), values)); + AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteContent), values)); } foreach (var next in StatusFlow.Next(Status)) diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs index 2b8da5114..39ec2ff60 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs @@ -109,7 +109,7 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models if (controller.HasPermission(Permissions.AppRulesDelete)) { - AddPutLink("delete", controller.Url(x => nameof(x.DeleteRule), values)); + AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteRule), values)); } return this; diff --git a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs index 201e3dadd..a513495ab 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs @@ -80,7 +80,10 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models AddPutLink("update", controller.Url(x => nameof(x.PutEvent), values)); - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteEvent), values)); + if (NextAttempt.HasValue) + { + AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteEvent), values)); + } return this; } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs index 97ad65125..bc583bb03 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs @@ -83,14 +83,16 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models } else { - AddPutLink("show", controller.Url(x => nameof(x.DisableField), values)); + AddPutLink("disable", controller.Url(x => nameof(x.DisableField), values)); } if (Properties is ArrayFieldPropertiesDto) { - AddPostLink("fields/add", controller.Url(x => nameof(x.PostNestedField), values)); + var parentValues = new { app, name = schema, parentId = FieldId }; - AddPutLink("order", controller.Url(x => nameof(x.PutNestedFieldOrdering), values)); + AddPostLink("fields/add", controller.Url(x => nameof(x.PostNestedField), parentValues)); + + AddPutLink("fields/order", controller.Url(x => nameof(x.PutNestedFieldOrdering), parentValues)); } AddPutLink("lock", controller.Url(x => nameof(x.LockField), values)); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs index 7a48b73d9..35fafd420 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs @@ -119,7 +119,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models if (allowUpdate) { - AddPutLink("order", controller.Url(x => nameof(x.PutSchemaFieldOrdering), values)); + AddPutLink("fields/order", controller.Url(x => nameof(x.PutSchemaFieldOrdering), values)); AddPutLink("update", controller.Url(x => nameof(x.PutSchema), values)); AddPutLink("update/category", controller.Url(x => nameof(x.PutCategory), values)); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs index 55e68584b..6c50117dc 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs @@ -513,9 +513,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas [ApiCosts(1)] public async Task DeleteNestedField(string app, string name, long parentId, long id) { - await CommandBus.PublishAsync(new DeleteField { ParentFieldId = parentId, FieldId = id }); + var command = new DeleteField { ParentFieldId = parentId, FieldId = id }; - return NoContent(); + var response = await InvokeCommandAsync(app, command); + + return Ok(response); } private async Task InvokeCommandAsync(string app, ICommand command) diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.ts b/src/Squidex/app/features/content/pages/content/content-page.component.ts index 94c3f79c2..fd4afb1a6 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.ts +++ b/src/Squidex/app/features/content/pages/content/content-page.component.ts @@ -183,7 +183,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD private loadContent(data: any) { this.contentForm.loadContent(data); - this.contentForm.setEnabled(this.content && !this.content.canUpdate); + this.contentForm.setEnabled(!this.content || this.content.canUpdate); } public discardChanges() { diff --git a/src/Squidex/app/features/content/shared/content-item.component.html b/src/Squidex/app/features/content/shared/content-item.component.html index 1b84a7d20..2569c05e1 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.html +++ b/src/Squidex/app/features/content/shared/content-item.component.html @@ -11,7 +11,7 @@ - + diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html index f1ae6a0f7..da925bae6 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html +++ b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html @@ -22,13 +22,13 @@ - @@ -38,12 +38,12 @@
-
- +
diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts index f169a8ff6..a3e1f140b 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts @@ -29,9 +29,6 @@ export class ContentChangedTriggerComponent implements OnInit { @Input() public schemas: ImmutableArray; - @Input() - public isEditable: boolean; - @Input() public trigger: any; diff --git a/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html b/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html index 1b4866640..cf9681be6 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html @@ -65,6 +65,7 @@
this.field).subscribe(); + this.schemasState.orderFields(this.schema, fields, this.field).subscribe(); } public lockField() { diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts index abc5b1f46..d9957a1f0 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts @@ -87,7 +87,7 @@ export class SchemaPageComponent extends ResourceOwner implements OnInit { } public sortFields(fields: FieldDto[]) { - this.schemasState.sortFields(this.schema, fields).subscribe(); + this.schemasState.orderFields(this.schema, fields).subscribe(); } public trackByField(index: number, field: FieldDto) { diff --git a/src/Squidex/app/features/settings/pages/patterns/pattern.component.ts b/src/Squidex/app/features/settings/pages/patterns/pattern.component.ts index 43572f708..b5cbec0a8 100644 --- a/src/Squidex/app/features/settings/pages/patterns/pattern.component.ts +++ b/src/Squidex/app/features/settings/pages/patterns/pattern.component.ts @@ -25,7 +25,7 @@ export class PatternComponent implements OnChanges { public editForm = new EditPatternForm(this.formBuilder); - public isEditable = false; + public isEditable = true; public isDeletable = false; constructor( diff --git a/src/Squidex/app/shared/components/asset.component.html b/src/Squidex/app/shared/components/asset.component.html index 583b66749..7fe2798ce 100644 --- a/src/Squidex/app/shared/components/asset.component.html +++ b/src/Squidex/app/shared/components/asset.component.html @@ -26,7 +26,10 @@ - + @@ -109,7 +112,10 @@ -