From f42a47d9aed401bd04801fae166438bca563bc61 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 2 Jul 2019 16:31:17 +0200 Subject: [PATCH] Temp --- .../Contents/Workflows.cs | 8 ++ .../Contents/DynamicContentWorkflow.cs | 33 ++++++-- src/Squidex.Shared/Permissions.cs | 4 +- .../Contents/ContentsController.cs | 2 +- .../Generator/SchemaSwaggerGenerator.cs | 4 +- .../Areas/Api/Controllers/Contents/Helper.cs | 23 ------ .../Controllers/Contents/Models/ContentDto.cs | 21 +++-- .../Contents/Models/ContentsDto.cs | 2 +- .../Contents/DynamicContentWorkflowTests.cs | 80 +++++++++++++++++-- 9 files changed, 123 insertions(+), 54 deletions(-) delete mode 100644 src/Squidex/Areas/Api/Controllers/Contents/Helper.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs index 504ab5d79..b5d86740c 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs @@ -50,6 +50,14 @@ namespace Squidex.Domain.Apps.Core.Contents return new Workflows(With(Guid.Empty, workflow)); } + [Pure] + public Workflows Set(Guid id, Workflow workflow) + { + Guard.NotNull(workflow, nameof(workflow)); + + return new Workflows(With(id, workflow)); + } + [Pure] public Workflows Update(Guid id, Workflow workflow) { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs b/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs index 6a302fcce..20d15e902 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/DynamicContentWorkflow.cs @@ -34,21 +34,21 @@ namespace Squidex.Domain.Apps.Entities.Contents public async Task GetAllAsync(ISchemaEntity schema) { - var workflow = await GetWorkflowAsync(schema.AppId.Id); + var workflow = await GetWorkflowAsync(schema.AppId.Id, schema.Id); return workflow.Steps.Select(x => new StatusInfo(x.Key, GetColor(x.Value))).ToArray(); } public async Task CanMoveToAsync(IContentEntity content, Status next, ClaimsPrincipal user) { - var workflow = await GetWorkflowAsync(content.AppId.Id); + var workflow = await GetWorkflowAsync(content.AppId.Id, content.SchemaId.Id); return workflow.TryGetTransition(content.Status, next, out var transition) && CanUse(transition, content, user); } public async Task CanUpdateAsync(IContentEntity content) { - var workflow = await GetWorkflowAsync(content.AppId.Id); + var workflow = await GetWorkflowAsync(content.AppId.Id, content.SchemaId.Id); if (workflow.TryGetStep(content.Status, out var step)) { @@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Contents public async Task GetInfoAsync(IContentEntity content) { - var workflow = await GetWorkflowAsync(content.AppId.Id); + var workflow = await GetWorkflowAsync(content.AppId.Id, content.SchemaId.Id); if (workflow.TryGetStep(content.Status, out var step)) { @@ -72,7 +72,7 @@ namespace Squidex.Domain.Apps.Entities.Contents public async Task GetInitialStatusAsync(ISchemaEntity schema) { - var workflow = await GetWorkflowAsync(schema.AppId.Id); + var workflow = await GetWorkflowAsync(schema.AppId.Id, schema.Id); var (status, step) = workflow.GetInitialStep(); @@ -83,7 +83,7 @@ namespace Squidex.Domain.Apps.Entities.Contents { var result = new List(); - var workflow = await GetWorkflowAsync(content.AppId.Id); + var workflow = await GetWorkflowAsync(content.AppId.Id, content.SchemaId.Id); foreach (var (to, step, transition) in workflow.GetTransitions(content.Status)) { @@ -114,11 +114,28 @@ namespace Squidex.Domain.Apps.Entities.Contents return true; } - private async Task GetWorkflowAsync(Guid appId) + private async Task GetWorkflowAsync(Guid appId, Guid schemaId) { + Workflow result = null; + var app = await appProvider.GetAppAsync(appId); - return app?.Workflows.GetFirst(); + if (app != null) + { + result = app.Workflows.Values.FirstOrDefault(x => x.SchemaIds.Contains(schemaId)); + + if (result == null) + { + result = app.Workflows.Values.FirstOrDefault(x => x.SchemaIds.Count == 0); + } + } + + if (result == null) + { + result = Workflow.Default; + } + + return result; } private static string GetColor(WorkflowStep step) diff --git a/src/Squidex.Shared/Permissions.cs b/src/Squidex.Shared/Permissions.cs index 62329248e..10ceb8fef 100644 --- a/src/Squidex.Shared/Permissions.cs +++ b/src/Squidex.Shared/Permissions.cs @@ -121,8 +121,8 @@ namespace Squidex.Shared public const string AppContentsRead = "squidex.apps.{app}.contents.{name}.read"; public const string AppContentsCreate = "squidex.apps.{app}.contents.{name}.create"; public const string AppContentsUpdate = "squidex.apps.{app}.contents.{name}.update"; - public const string AppContentsStatus = "squidex.apps.{app}.contents.{name}.status.{status}"; - public const string AppContentsDiscard = "squidex.apps.{app}.contents.{name}.discard"; + public const string AppContentsDraftDiscard = "squidex.apps.{app}.contents.{name}.draft.discard"; + public const string AppContentsDraftPublish = "squidex.apps.{app}.contents.{name}.draft.publish"; public const string AppContentsDelete = "squidex.apps.{app}.contents.{name}.delete"; public const string AppApi = "squidex.apps.{app}.api"; diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index 095be8d07..6ddd9970d 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -396,7 +396,7 @@ namespace Squidex.Areas.Api.Controllers.Contents [HttpPut] [Route("content/{app}/{name}/{id}/discard/")] [ProducesResponseType(typeof(ContentsDto), 200)] - [ApiPermission(Permissions.AppContentsDiscard)] + [ApiPermission(Permissions.AppContentsDraftDiscard)] [ApiCosts(1)] public async Task DiscardDraft(string app, string name, Guid id) { diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs index 4aad54547..bda1b710d 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Generator/SchemaSwaggerGenerator.cs @@ -194,7 +194,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator operation.AddResponse("204", $"{schemaName} content status changed.", contentSchema); operation.AddResponse("400", "Content data valid."); - AddSecurity(operation, Permissions.AppContentsStatus); + AddSecurity(operation, Permissions.AppContentsMove); }); } @@ -209,7 +209,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Generator operation.AddResponse("400", "No pending draft."); operation.AddResponse("200", $"{schemaName} content status changed.", contentSchema); - AddSecurity(operation, Permissions.AppContentsDiscard); + AddSecurity(operation, Permissions.AppContentsDraftDiscard); }); } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Helper.cs b/src/Squidex/Areas/Api/Controllers/Contents/Helper.cs deleted file mode 100644 index 8644c925a..000000000 --- a/src/Squidex/Areas/Api/Controllers/Contents/Helper.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Squidex.Domain.Apps.Core.Contents; -using Squidex.Infrastructure.Security; -using Squidex.Shared; - -namespace Squidex.Areas.Api.Controllers.Contents -{ - public static class Helper - { - public static Permission StatusPermission(string app, string schema, Status status) - { - var id = Permissions.AppContentsStatus.Replace("{status}", status.Name); - - return Permissions.ForApp(id, app, schema); - } - } -} diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs index 0725239e4..01775bd9c 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs @@ -122,12 +122,12 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models if (IsPending) { - if (controller.HasPermission(Permissions.AppContentsDiscard, app, schema)) + if (controller.HasPermission(Permissions.AppContentsDraftDiscard, app, schema)) { AddPutLink("draft/discard", controller.Url(x => nameof(x.DiscardDraft), values)); } - if (controller.HasPermission(Helper.StatusPermission(app, schema, Status.Published))) + if (controller.HasPermission(Permissions.AppContentsDraftPublish, app, schema)) { AddPutLink("draft/publish", controller.Url(x => nameof(x.PutContentStatus), values)); } @@ -146,24 +146,21 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models } AddPatchLink("patch", controller.Url(x => nameof(x.PatchContent), values)); - } - - if (controller.HasPermission(Permissions.AppContentsDelete, app, schema)) - { - AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteContent), values)); - } - if (content.Nexts != null) - { - foreach (var next in content.Nexts) + if (content.Nexts != null) { - if (controller.HasPermission(Helper.StatusPermission(app, schema, next.Status))) + foreach (var next in content.Nexts) { AddPutLink($"status/{next.Status}", controller.Url(x => nameof(x.PutContentStatus), values), next.Color); } } } + if (controller.HasPermission(Permissions.AppContentsDelete, app, schema)) + { + AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteContent), values)); + } + return this; } } diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs index 749e662d1..ebf991903 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs @@ -80,7 +80,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models { AddPostLink("create", controller.Url(x => nameof(x.PostContent), values)); - if (controller.HasPermission(Helper.StatusPermission(app, schema, Status.Published))) + if (controller.HasPermission(Permissions.AppContentsCreatePublished, app, schema)) { AddPostLink("create/publish", controller.Url(x => nameof(x.PostContent), values) + "?publish=true"); } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs index 027bc03c1..ccffa1c8c 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/DynamicContentWorkflowTests.cs @@ -23,6 +23,8 @@ namespace Squidex.Domain.Apps.Entities.Contents public class DynamicContentWorkflowTests { private readonly NamedId appId = NamedId.Of(Guid.NewGuid(), "my-app"); + private readonly NamedId schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); + private readonly NamedId simpleSchemaId = NamedId.Of(Guid.NewGuid(), "my-simple-schema"); private readonly IAppProvider appProvider = A.Fake(); private readonly IAppEntity appEntity = A.Fake(); private readonly DynamicContentWorkflow sut; @@ -56,13 +58,38 @@ namespace Squidex.Domain.Apps.Entities.Contents StatusColors.Published) }); + private readonly Workflow simpleWorkflow; + public DynamicContentWorkflowTests() { + simpleWorkflow = new Workflow( + Status.Draft, + new Dictionary + { + [Status.Draft] = + new WorkflowStep( + new Dictionary + { + [Status.Published] = new WorkflowTransition() + }, + StatusColors.Draft), + [Status.Published] = + new WorkflowStep( + new Dictionary + { + [Status.Draft] = new WorkflowTransition() + }, + StatusColors.Published) + }, + new List { simpleSchemaId.Id }); + + var workflows = Workflows.Empty.Set(workflow).Set(Guid.NewGuid(), simpleWorkflow); + A.CallTo(() => appProvider.GetAppAsync(appId.Id)) .Returns(appEntity); A.CallTo(() => appEntity.Workflows) - .Returns(Workflows.Empty.Set(workflow)); + .Returns(workflows); sut = new DynamicContentWorkflow(new JintScriptEngine(), appProvider); } @@ -229,24 +256,67 @@ namespace Squidex.Domain.Apps.Entities.Contents result.Should().BeEquivalentTo(expected); } - private ISchemaEntity CreateSchema() + [Fact] + public async Task Should_return_all_statuses_for_simple_schema_workflow() + { + var expected = new[] + { + new StatusInfo(Status.Draft, StatusColors.Draft), + new StatusInfo(Status.Published, StatusColors.Published) + }; + + var result = await sut.GetAllAsync(CreateSchema(true)); + + result.Should().BeEquivalentTo(expected); + } + + [Fact] + public async Task Should_return_all_statuses_for_default_workflow_when_no_workflow_configured() + { + A.CallTo(() => appEntity.Workflows).Returns(Workflows.Empty); + + var expected = new[] + { + new StatusInfo(Status.Archived, StatusColors.Archived), + new StatusInfo(Status.Draft, StatusColors.Draft), + new StatusInfo(Status.Published, StatusColors.Published) + }; + + var result = await sut.GetAllAsync(CreateSchema(true)); + + result.Should().BeEquivalentTo(expected); + } + + private ISchemaEntity CreateSchema(bool simple = false) { var schema = A.Fake(); A.CallTo(() => schema.AppId).Returns(appId); + A.CallTo(() => schema.Id).Returns(simple ? simpleSchemaId.Id : schemaId.Id); return schema; } - private IContentEntity CreateContent(Status status, int value) + private IContentEntity CreateContent(Status status, int value, bool simple = false) { - var data = + var content = new ContentEntity { AppId = appId, Status = status }; + + if (simple) + { + content.SchemaId = simpleSchemaId; + } + else + { + content.SchemaId = schemaId; + } + + content.DataDraft = new NamedContentData() .AddField("field", new ContentFieldData() .AddValue("iv", value)); - return new ContentEntity { AppId = appId, Status = status, DataDraft = data }; + return content; } private ClaimsPrincipal User(string role)