mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
142 changed files with 2585 additions and 958 deletions
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Domain.Apps.Core.Tags |
|||
{ |
|||
public sealed class TagsSet : Dictionary<string, int> |
|||
{ |
|||
public long Version { get; set; } |
|||
|
|||
public TagsSet() |
|||
{ |
|||
} |
|||
|
|||
public TagsSet(IDictionary<string, int> tags, long version) |
|||
: base(tags) |
|||
{ |
|||
Version = version; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Apps.Commands |
|||
{ |
|||
public sealed class AddWorkflow : AppCommand |
|||
{ |
|||
public Guid WorkflowId { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public AddWorkflow() |
|||
{ |
|||
WorkflowId = Guid.NewGuid(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents.Commands |
|||
{ |
|||
public abstract class ContentUpdateCommand : ContentDataCommand |
|||
{ |
|||
public bool AsDraft { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
// ==========================================================================
|
|||
// 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.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents |
|||
{ |
|||
public sealed class DefaultWorkflowsValidator : IWorkflowsValidator |
|||
{ |
|||
private readonly IAppProvider appProvider; |
|||
|
|||
public DefaultWorkflowsValidator(IAppProvider appProvider) |
|||
{ |
|||
Guard.NotNull(appProvider, nameof(appProvider)); |
|||
|
|||
this.appProvider = appProvider; |
|||
} |
|||
|
|||
public async Task<IReadOnlyList<string>> ValidateAsync(Guid appId, Workflows workflows) |
|||
{ |
|||
Guard.NotNull(workflows, nameof(workflows)); |
|||
|
|||
var errors = new List<string>(); |
|||
|
|||
if (workflows.Values.Count(x => x.SchemaIds.Count == 0) > 1) |
|||
{ |
|||
errors.Add("Multiple workflows cover all schemas."); |
|||
} |
|||
|
|||
var uniqueSchemaIds = workflows.Values.SelectMany(x => x.SchemaIds).Distinct().ToList(); |
|||
|
|||
foreach (var schemaId in uniqueSchemaIds) |
|||
{ |
|||
if (workflows.Values.Count(x => x.SchemaIds.Contains(schemaId)) > 1) |
|||
{ |
|||
var schema = await appProvider.GetSchemaAsync(appId, schemaId); |
|||
|
|||
if (schema != null) |
|||
{ |
|||
errors.Add($"The schema `{schema.SchemaDef.Name}` is covered by multiple workflows."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return errors; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.Contents |
|||
{ |
|||
public interface IWorkflowsValidator |
|||
{ |
|||
Task<IReadOnlyList<string>> ValidateAsync(Guid appId, Workflows workflows); |
|||
} |
|||
} |
|||
@ -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.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Apps |
|||
{ |
|||
[EventType(nameof(AppWorkflowAdded))] |
|||
public sealed class AppWorkflowAdded : AppEvent |
|||
{ |
|||
public Guid WorkflowId { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.EventSourcing; |
|||
|
|||
namespace Squidex.Domain.Apps.Events.Apps |
|||
{ |
|||
[EventType(nameof(AppWorkflowDeleted))] |
|||
public sealed class AppWorkflowDeleted : AppEvent |
|||
{ |
|||
public Guid WorkflowId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure; |
|||
|
|||
namespace Squidex.Web |
|||
{ |
|||
public struct Deferred |
|||
{ |
|||
private readonly Lazy<Task<object>> value; |
|||
|
|||
public Task<object> Value |
|||
{ |
|||
get { return value.Value; } |
|||
} |
|||
|
|||
private Deferred(Func<Task<object>> value) |
|||
{ |
|||
this.value = new Lazy<Task<object>>(value); |
|||
} |
|||
|
|||
public static Deferred Response(Func<object> factory) |
|||
{ |
|||
Guard.NotNull(factory, nameof(factory)); |
|||
|
|||
return new Deferred(() => Task.FromResult(factory())); |
|||
} |
|||
|
|||
public static Deferred AsyncResponse<T>(Func<Task<T>> factory) |
|||
{ |
|||
Guard.NotNull(factory, nameof(factory)); |
|||
|
|||
return new Deferred(async () => await factory()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.Filters; |
|||
|
|||
namespace Squidex.Web.Pipeline |
|||
{ |
|||
public sealed class DeferredActionFilter : IAsyncActionFilter |
|||
{ |
|||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) |
|||
{ |
|||
var resultContext = await next(); |
|||
|
|||
if (resultContext.Result is ObjectResult objectResult && objectResult.Value is Deferred deferred) |
|||
{ |
|||
objectResult.Value = await deferred.Value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
using Squidex.Domain.Apps.Entities.Contents; |
|||
using Squidex.Shared; |
|||
using Squidex.Web; |
|||
|
|||
namespace Squidex.Areas.Api.Controllers.Apps.Models |
|||
{ |
|||
public sealed class WorkflowsDto : Resource |
|||
{ |
|||
/// <summary>
|
|||
/// The workflow.
|
|||
/// </summary>
|
|||
[Required] |
|||
public WorkflowDto[] Items { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// The errros that should be fixed.
|
|||
/// </summary>
|
|||
[Required] |
|||
public string[] Errors { get; set; } |
|||
|
|||
public static async Task<WorkflowsDto> FromAppAsync(IWorkflowsValidator workflowsValidator, IAppEntity app, ApiController controller) |
|||
{ |
|||
var result = new WorkflowsDto |
|||
{ |
|||
Items = app.Workflows.Select(x => WorkflowDto.FromWorkflow(x.Key, x.Value, controller, app.Name)).ToArray(), |
|||
}; |
|||
|
|||
var errors = await workflowsValidator.ValidateAsync(app.Id, app.Workflows); |
|||
|
|||
result.Errors = errors.ToArray(); |
|||
|
|||
return result.CreateLinks(controller, app.Name); |
|||
} |
|||
|
|||
private WorkflowsDto CreateLinks(ApiController controller, string app) |
|||
{ |
|||
var values = new { app }; |
|||
|
|||
AddSelfLink(controller.Url<AppWorkflowsController>(x => nameof(x.GetWorkflows), values)); |
|||
|
|||
if (controller.HasPermission(Permissions.AppWorkflowsCreate, app)) |
|||
{ |
|||
AddPostLink("create", controller.Url<AppWorkflowsController>(x => nameof(x.PostWorkflow), values)); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|||
*/ |
|||
|
|||
import { Converter, SchemaDto, TagValue } from '@app/shared'; |
|||
|
|||
export class SchemaTagConverter implements Converter { |
|||
public readonly suggestions: TagValue[]; |
|||
|
|||
constructor( |
|||
private readonly schemas: SchemaDto[] |
|||
) { |
|||
this.suggestions = schemas.map(x => new TagValue(x.id, x.name, x.id)); |
|||
} |
|||
|
|||
public convertInput(input: string): TagValue<any> | null { |
|||
const schema = this.schemas.find(x => x.name === input); |
|||
|
|||
if (schema) { |
|||
return new TagValue(schema.id, schema.name, schema.id); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public convertValue(value: any): TagValue<any> | null { |
|||
const schema = this.schemas.find(x => x.id === value); |
|||
|
|||
if (schema) { |
|||
return new TagValue(schema.id, schema.name, schema.id); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
<div class="table-items-row table-items-row-expandable workflow"> |
|||
<div class="table-items-row-summary"> |
|||
<div class="row no-gutters"> |
|||
<div class="col col-name"> |
|||
<span class="workflow-name">{{workflow.displayName}}</span> |
|||
</div> |
|||
<div class="col col-tags"> |
|||
<sqx-tag-editor [converter]="schemasSource" [ngModel]="workflow.schemaIds" |
|||
placeholder="" |
|||
styleGray="true" |
|||
styleBlank="true" |
|||
singleLine="true" |
|||
disabled="true"> |
|||
</sqx-tag-editor> |
|||
</div> |
|||
<div class="col-options"> |
|||
<div class="float-right"> |
|||
<button type="button" class="btn btn-secondary table-items-edit-button mr-1" [class.active]="isEditing" (click)="toggleEditing()"> |
|||
<i class="icon-settings"></i> |
|||
</button> |
|||
|
|||
<button type="button" class="btn btn-text-danger" |
|||
[disabled]="!workflow.canDelete" |
|||
(sqxConfirmClick)="remove()" |
|||
confirmTitle="Remove workflow" |
|||
confirmText="Do you really want to remove the workflow?"> |
|||
<i class="icon-bin2"></i> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="table-items-row-details" *ngIf="isEditing"> |
|||
<div class="table-items-row-details-tabs clearfix"> |
|||
<div class="float-right"> |
|||
<button type="reset" class="btn btn-text" (click)="toggleEditing()">Cancel</button> |
|||
<button type="submit" class="btn btn-primary" *ngIf="isEditable" (click)="save()">Save</button> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="table-items-row-details-tab"> |
|||
<sqx-form-error [error]="error"></sqx-form-error> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col-form-label" for="{{workflow.id}}_name">Name</label> |
|||
|
|||
<div class="col"> |
|||
<input class="form-control" id="{{workflow.id}}_name" |
|||
[ngModelOptions]="onBlur" |
|||
[ngModel]="workflow.name" |
|||
(ngModelChange)="rename($event)" /> |
|||
|
|||
<sqx-form-hint> |
|||
Optional name for the workflow. |
|||
</sqx-form-hint> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group row"> |
|||
<label class="col-form-label" for="{{workflow.id}}_schemas">Schemas</label> |
|||
|
|||
<div class="col"> |
|||
<sqx-tag-editor placeholder=", to add schema" |
|||
[converter]="schemasSource" |
|||
[ngModel]="workflow.schemaIds" |
|||
(ngModelChange)="changeSchemaIds($event)" |
|||
[suggestedValues]="schemasSource.suggestions"> |
|||
</sqx-tag-editor> |
|||
|
|||
<sqx-form-hint> |
|||
Restrict this workflow to specific schemas or keep it empty for all schemas. |
|||
</sqx-form-hint> |
|||
</div> |
|||
</div> |
|||
|
|||
<sqx-workflow-step *ngFor="let step of workflow.steps; trackBy: trackByStep" |
|||
[workflow]="workflow" |
|||
[disabled]="!workflow.canUpdate" |
|||
[roles]="roles" |
|||
[step]="step" |
|||
(makeInitial)="setInitial(step)" |
|||
(rename)="renameStep(step, $event)" |
|||
(remove)="removeStep(step)" |
|||
(transitionAdd)="addTransiton(step, $event)" |
|||
(transitionRemove)="removeTransition(step, $event)" |
|||
(transitionUpdate)="updateTransition($event)" |
|||
(update)="updateStep(step, $event)"> |
|||
</sqx-workflow-step> |
|||
|
|||
<button class="btn btn-success" (click)="addStep()" *ngIf="workflow.canUpdate"> |
|||
Add Step |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,34 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.table-items-row-details { |
|||
&::before { |
|||
right: 4.55rem; |
|||
} |
|||
} |
|||
|
|||
.workflow { |
|||
&-name { |
|||
@include truncate; |
|||
} |
|||
} |
|||
|
|||
.col-form-label { |
|||
min-width: 4rem; |
|||
max-width: 4rem; |
|||
} |
|||
|
|||
.col-tags { |
|||
padding: .6rem 1rem; |
|||
padding-bottom: 0; |
|||
} |
|||
|
|||
.form-group { |
|||
margin-bottom: 2rem; |
|||
margin-left: 2rem; |
|||
} |
|||
|
|||
.btn-success { |
|||
margin-bottom: 1rem; |
|||
margin-left: 2rem; |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue