mirror of https://github.com/Squidex/squidex.git
19 changed files with 420 additions and 48 deletions
@ -0,0 +1,111 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Contents |
|||
{ |
|||
public sealed class Workflow |
|||
{ |
|||
public static readonly Workflow Default = new Workflow( |
|||
new Dictionary<Status, WorkflowStep> |
|||
{ |
|||
[Status.Archived] = |
|||
new WorkflowStep( |
|||
new Dictionary<Status, WorkflowTransition> |
|||
{ |
|||
[Status.Draft] = new WorkflowTransition() |
|||
}, |
|||
StatusColors.Archived, true), |
|||
[Status.Draft] = |
|||
new WorkflowStep( |
|||
new Dictionary<Status, WorkflowTransition> |
|||
{ |
|||
[Status.Archived] = new WorkflowTransition(), |
|||
[Status.Published] = new WorkflowTransition() |
|||
}, |
|||
StatusColors.Draft), |
|||
[Status.Published] = |
|||
new WorkflowStep( |
|||
new Dictionary<Status, WorkflowTransition> |
|||
{ |
|||
[Status.Archived] = new WorkflowTransition(), |
|||
[Status.Published] = new WorkflowTransition() |
|||
}, |
|||
StatusColors.Archived) |
|||
}, Status.Draft); |
|||
|
|||
public IReadOnlyDictionary<Status, WorkflowStep> Steps { get; } |
|||
|
|||
public Status Initial { get; } |
|||
|
|||
public Workflow(IReadOnlyDictionary<Status, WorkflowStep> steps, Status initial) |
|||
{ |
|||
Guard.NotNull(steps, nameof(steps)); |
|||
|
|||
Steps = steps; |
|||
|
|||
Initial = initial; |
|||
} |
|||
|
|||
public static Workflow Create(IReadOnlyDictionary<Status, WorkflowStep> steps, Status initial) |
|||
{ |
|||
Guard.NotNull(steps, nameof(steps)); |
|||
|
|||
foreach (var step in steps.Values) |
|||
{ |
|||
foreach (var transition in step.Transitions) |
|||
{ |
|||
if (steps.ContainsKey(transition.Key)) |
|||
{ |
|||
throw new ArgumentException("Transitions ends to an unknown step.", nameof(initial)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (steps.ContainsKey(initial)) |
|||
{ |
|||
throw new ArgumentException("Initial step not known.", nameof(initial)); |
|||
} |
|||
|
|||
return new Workflow(steps, initial); |
|||
} |
|||
|
|||
public IEnumerable<(Status Status, WorkflowStep Step, WorkflowTransition Transition)> GetTransitions(Status status) |
|||
{ |
|||
if (TryGetStep(status, out var step)) |
|||
{ |
|||
foreach (var transition in step.Transitions) |
|||
{ |
|||
yield return (transition.Key, Steps[transition.Key], transition.Value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public WorkflowTransition GetTransition(Status from, Status to) |
|||
{ |
|||
if (TryGetStep(from, out var step) && step.Transitions.TryGetValue(to, out var transition)) |
|||
{ |
|||
return transition; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public bool TryGetStep(Status status, out WorkflowStep step) |
|||
{ |
|||
return Steps.TryGetValue(status, out step); |
|||
} |
|||
|
|||
public (Status Key, WorkflowStep) GetInitialStep() |
|||
{ |
|||
return (Initial, Steps[Initial]); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Contents |
|||
{ |
|||
public sealed class WorkflowStep |
|||
{ |
|||
public IReadOnlyDictionary<Status, WorkflowTransition> Transitions { get; } |
|||
|
|||
public string Color { get; } |
|||
|
|||
public bool NoUpdate { get; } |
|||
|
|||
public WorkflowStep(IReadOnlyDictionary<Status, WorkflowTransition> transitions, string color, bool noUpdate = false) |
|||
{ |
|||
Guard.NotNull(transitions, nameof(transitions)); |
|||
|
|||
Transitions = transitions; |
|||
|
|||
Color = color; |
|||
|
|||
NoUpdate = noUpdate; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Core.Contents |
|||
{ |
|||
public sealed class WorkflowTransition |
|||
{ |
|||
public string Expression { get; } |
|||
|
|||
public string Role { get; } |
|||
|
|||
public WorkflowTransition(string expression = null, string role = null) |
|||
{ |
|||
Expression = expression; |
|||
|
|||
Role = role; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics.Contracts; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Collections; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Contents |
|||
{ |
|||
public sealed class Workflows : ArrayDictionary<Guid, Workflows> |
|||
{ |
|||
public static readonly Workflows Empty = new Workflows(); |
|||
|
|||
private Workflows() |
|||
{ |
|||
} |
|||
|
|||
public Workflows(KeyValuePair<Guid, Workflows>[] items) |
|||
: base(items) |
|||
{ |
|||
} |
|||
|
|||
[Pure] |
|||
public Workflows Set(Workflow workflow) |
|||
{ |
|||
Guard.NotNull(workflow, nameof(workflow)); |
|||
|
|||
return new Workflows(With(Guid.Empty, workflow)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,131 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Core.Scripting; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents |
|||
{ |
|||
public sealed class DynamicContentWorkflow : IContentWorkflow |
|||
{ |
|||
private readonly IScriptEngine scriptEngine; |
|||
private readonly IAppProvider appProvider; |
|||
|
|||
public DynamicContentWorkflow(IScriptEngine scriptEngine, IAppProvider appProvider) |
|||
{ |
|||
Guard.NotNull(scriptEngine, nameof(scriptEngine)); |
|||
Guard.NotNull(appProvider, nameof(appProvider)); |
|||
|
|||
this.scriptEngine = scriptEngine; |
|||
|
|||
this.appProvider = appProvider; |
|||
} |
|||
|
|||
public async Task<StatusInfo[]> GetAllAsync(ISchemaEntity schema) |
|||
{ |
|||
var workflow = await GetWorkflowAsync(schema.AppId.Id); |
|||
|
|||
return workflow.Steps.Select(x => new StatusInfo(x.Key, GetColor(x.Value))).ToArray(); |
|||
} |
|||
|
|||
public async Task<bool> CanMoveToAsync(IContentEntity content, Status next, ClaimsPrincipal user) |
|||
{ |
|||
var workflow = await GetWorkflowAsync(content.AppId.Id); |
|||
|
|||
var transition = workflow.GetTransition(content.Status, next); |
|||
|
|||
return transition != null && CanUse(transition, content, user); |
|||
} |
|||
|
|||
public async Task<bool> CanUpdateAsync(IContentEntity content) |
|||
{ |
|||
var workflow = await GetWorkflowAsync(content.AppId.Id); |
|||
|
|||
if (workflow.TryGetStep(content.Status, out var step)) |
|||
{ |
|||
return !step.NoUpdate; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public async Task<StatusInfo> GetInfoAsync(IContentEntity content) |
|||
{ |
|||
var workflow = await GetWorkflowAsync(content.AppId.Id); |
|||
|
|||
if (workflow.TryGetStep(content.Status, out var step)) |
|||
{ |
|||
return new StatusInfo(content.Status, GetColor(step)); |
|||
} |
|||
|
|||
return new StatusInfo(content.Status, StatusColors.Draft); |
|||
} |
|||
|
|||
public async Task<StatusInfo> GetInitialStatusAsync(ISchemaEntity schema) |
|||
{ |
|||
var workflow = await GetWorkflowAsync(schema.AppId.Id); |
|||
|
|||
var (status, step) = workflow.GetInitialStep(); |
|||
|
|||
return new StatusInfo(status, GetColor(step)); |
|||
} |
|||
|
|||
public async Task<StatusInfo[]> GetNextsAsync(IContentEntity content, ClaimsPrincipal user) |
|||
{ |
|||
var result = new List<StatusInfo>(); |
|||
|
|||
var workflow = await GetWorkflowAsync(content.AppId.Id); |
|||
|
|||
foreach (var (to, step, transition) in workflow.GetTransitions(content.Status)) |
|||
{ |
|||
if (CanUse(transition, content, user)) |
|||
{ |
|||
result.Add(new StatusInfo(to, GetColor(step))); |
|||
} |
|||
} |
|||
|
|||
return result.ToArray(); |
|||
} |
|||
|
|||
private bool CanUse(WorkflowTransition transition, IContentEntity content, ClaimsPrincipal user) |
|||
{ |
|||
if (!string.IsNullOrWhiteSpace(transition.Role)) |
|||
{ |
|||
if (!user.Claims.Any(x => x.Type == ClaimTypes.Role && x.Value == transition.Role)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
if (!string.IsNullOrWhiteSpace(transition.Expression)) |
|||
{ |
|||
return scriptEngine.Evaluate("data", content.DataDraft, transition.Expression); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private async Task<Workflow> GetWorkflowAsync(Guid appId) |
|||
{ |
|||
var app = await appProvider.GetAppAsync(appId); |
|||
|
|||
return app?.Workflows.Values?.FirstOrDefault() ?? Workflow.Default; |
|||
} |
|||
|
|||
private static string GetColor(WorkflowStep step) |
|||
{ |
|||
return step.Color ?? StatusColors.Draft; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue