diff --git a/README.md b/README.md index 9c83fa2af..e8bd15355 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Squidex is an open source headless CMS and content management hub. In contrast to a traditional CMS Squidex provides a rich API with OData filter and Swagger definitions. It is up to you to build your UI on top of it. It can be website, a native app or just another server. We build it with ASP.NET Core and CQRS and is tested for Windows and Linux on modern browsers. -[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=square)](https://gitter.im/squidex-cms/Lobby) [![Slack](https://img.shields.io/badge/chat-on_slack-E01765.svg?style=square)](https://squidex-slack.herokuapp.com/) [![Build Status](http://build.squidex.io/api/badges/Squidex/squidex/status.svg)](http://build.squidex.io/Squidex/squidex) +[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=square)](https://gitter.im/squidex-cms/Lobby) [![Slack](https://img.shields.io/badge/chat-on_slack-E01765.svg?style=square)](https://squidex-slack.herokuapp.com/) [![Build Status](http://build.squidex.io/api/badges/Squidex/squidex/status.svg)](http://build.squidex.io/Squidex/squidex) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FSquidex%2Fsquidex.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FSquidex%2Fsquidex?ref=badge_shield) Read the docs at [https://docs.squidex.io/](https://docs.squidex.io/) (work in progress) or just check out the code and play around. @@ -40,3 +40,7 @@ Please create issues to report bugs, suggest new functionalities, ask questions ## Cloud Version Although Squidex is free we are also working on a Saas version on [https://cloud.squidex.io](https://cloud.squidex.io) (More information coming soon). We have also have plans to sell a premium version with first class support and some exlusive features. But don't be afraid, our first priority is to deliver a state of the art, stable, fast and free content management hub to make the life for developers a little bit easier. + +## License + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FSquidex%2Fsquidex.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FSquidex%2Fsquidex?ref=badge_large) \ No newline at end of file diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidationExtensions.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidationExtensions.cs index 244aac11b..37f075a65 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidationExtensions.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidationExtensions.cs @@ -5,7 +5,9 @@ // 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.Domain.Apps.Core.Schemas; @@ -27,6 +29,18 @@ namespace Squidex.Domain.Apps.Core.ValidateContent } } + public static async Task ValidateAsync(this NamedContentData data, ValidationContext context, Schema schema, PartitionResolver partitionResolver, Func message) + { + var validator = new ContentValidator(schema, partitionResolver, context); + + await validator.ValidateAsync(data); + + if (validator.Errors.Count > 0) + { + throw new ValidationException(message(), validator.Errors.ToList()); + } + } + public static async Task ValidatePartialAsync(this NamedContentData data, ValidationContext context, Schema schema, PartitionResolver partitionResolver, IList errors) { var validator = new ContentValidator(schema, partitionResolver, context); @@ -38,5 +52,17 @@ namespace Squidex.Domain.Apps.Core.ValidateContent errors.Add(error); } } + + public static async Task ValidatePartialAsync(this NamedContentData data, ValidationContext context, Schema schema, PartitionResolver partitionResolver, Func message) + { + var validator = new ContentValidator(schema, partitionResolver, context); + + await validator.ValidatePartialAsync(data); + + if (validator.Errors.Count > 0) + { + throw new ValidationException(message(), validator.Errors.ToList()); + } + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs index 465a4aa6e..82129cc05 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentGrain.cs @@ -91,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Contents await operationContext.ExecuteScriptAndTransformAsync(x => x.ScriptCreate, "Create"); await operationContext.EnrichAsync(); - await operationContext.ValidateAsync(false); + await operationContext.ValidateAsync(); Create(c); @@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var operationContext = await CreateContext(c, () => "Failed to update content."); - await operationContext.ValidateAsync(true); + await operationContext.ValidateAsync(); await operationContext.ExecuteScriptAndTransformAsync(x => x.ScriptUpdate, "Update"); Update(c); @@ -120,7 +120,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var operationContext = await CreateContext(c, () => "Failed to patch content."); - await operationContext.ValidateAsync(true); + await operationContext.ValidatePartialAsync(); await operationContext.ExecuteScriptAndTransformAsync(x => x.ScriptUpdate, "Patch"); Patch(c); diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs index 82963a379..3cc500552 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.EnrichContent; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.ValidateContent; @@ -17,7 +18,6 @@ using Squidex.Domain.Apps.Entities.Assets.Repositories; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure; using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Entities.Contents @@ -31,6 +31,7 @@ namespace Squidex.Domain.Apps.Entities.Contents private IScriptEngine scriptEngine; private ISchemaEntity schemaEntity; private IAppEntity appEntity; + private Guid appId; private Func message; public static async Task CreateAsync( @@ -56,6 +57,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var context = new ContentOperationContext { appEntity = appEntity, + appId = a.Id, assetRepository = assetRepository, contentRepository = contentRepository, content = content, @@ -78,54 +80,35 @@ namespace Squidex.Domain.Apps.Entities.Contents return TaskHelper.Done; } - public async Task ValidateAsync(bool partial) + public Task ValidateAsync() { if (command is ContentDataCommand dataCommand) { - var errors = new List(); - - var ctx = - new ValidationContext( - (contentIds, schemaId) => - { - return QueryContentsAsync(content.AppId.Id, schemaId, contentIds); - }, - assetIds => - { - return QueryAssetsAsync(content.AppId.Id, assetIds); - }); - - if (partial) - { - await dataCommand.Data.ValidatePartialAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver(), errors); - } - else - { - await dataCommand.Data.ValidateAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver(), errors); - } + var ctx = CreateValidationContext(); - if (errors.Count > 0) - { - throw new ValidationException(message(), errors.ToArray()); - } + return dataCommand.Data.ValidateAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver(), message); } - } - private async Task> QueryAssetsAsync(Guid appId, IEnumerable assetIds) - { - return await assetRepository.QueryAsync(appId, new HashSet(assetIds)); + return TaskHelper.Done; } - private async Task> QueryContentsAsync(Guid appId, Guid schemaId, IEnumerable contentIds) + public Task ValidatePartialAsync() { - return await contentRepository.QueryNotFoundAsync(appId, schemaId, contentIds.ToList()); + if (command is ContentDataCommand dataCommand) + { + var ctx = CreateValidationContext(); + + return dataCommand.Data.ValidatePartialAsync(ctx, schemaEntity.SchemaDef, appEntity.PartitionResolver(), message); + } + + return TaskHelper.Done; } public Task ExecuteScriptAndTransformAsync(Func script, object operation) { if (command is ContentDataCommand dataCommand) { - var ctx = new ScriptContext { ContentId = content.Id, OldData = content.Data, User = command.User, Operation = operation.ToString(), Data = dataCommand.Data }; + var ctx = CreateScriptContext(operation, dataCommand.Data); dataCommand.Data = scriptEngine.ExecuteAndTransform(ctx, script(schemaEntity)); } @@ -135,11 +118,39 @@ namespace Squidex.Domain.Apps.Entities.Contents public Task ExecuteScriptAsync(Func script, object operation) { - var ctx = new ScriptContext { ContentId = content.Id, OldData = content.Data, User = command.User, Operation = operation.ToString() }; + var ctx = CreateScriptContext(operation, content.Data); scriptEngine.Execute(ctx, script(schemaEntity)); return TaskHelper.Done; } + + private ScriptContext CreateScriptContext(object operation, NamedContentData data = null) + { + return new ScriptContext { ContentId = command.ContentId, OldData = content.Data, Data = data, User = command.User, Operation = operation.ToString() }; + } + + private ValidationContext CreateValidationContext() + { + return new ValidationContext( + (contentIds, schemaId) => + { + return QueryContentsAsync(schemaId, contentIds); + }, + assetIds => + { + return QueryAssetsAsync(assetIds); + }); + } + + private async Task> QueryAssetsAsync(IEnumerable assetIds) + { + return await assetRepository.QueryAsync(appId, new HashSet(assetIds)); + } + + private async Task> QueryContentsAsync(Guid schemaId, IEnumerable contentIds) + { + return await contentRepository.QueryNotFoundAsync(appId, schemaId, contentIds.ToList()); + } } } diff --git a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs index 73d161006..8f724b1a9 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithAppIdCommandMiddleware.cs @@ -60,4 +60,4 @@ namespace Squidex.Pipeline.CommandMiddlewares return new NamedId(appFeature.App.Id, appFeature.App.Name); } } -} +} \ No newline at end of file diff --git a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs index 8e976d316..40b660c8a 100644 --- a/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs +++ b/src/Squidex/Pipeline/CommandMiddlewares/EnrichWithSchemaIdCommandMiddleware.cs @@ -101,4 +101,4 @@ namespace Squidex.Pipeline.CommandMiddlewares return null; } } -} +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/date-time-editor.component.ts b/src/Squidex/app/framework/angular/date-time-editor.component.ts index ad6103174..024d73b1d 100644 --- a/src/Squidex/app/framework/angular/date-time-editor.component.ts +++ b/src/Squidex/app/framework/angular/date-time-editor.component.ts @@ -191,19 +191,20 @@ export class DateTimeEditorComponent implements ControlValueAccessor, OnDestroy, } private updateControls() { - if (!this.dateValue) { - return; - } - this.suppressEvents = true; if (this.timeValue && this.timeValue.isValid()) { this.timeControl.setValue(this.timeValue.format('HH:mm:ss'), { emitEvent: false }); + } else { + this.timeControl.setValue(null, { emitEvent: false }); } + if (this.dateValue && this.dateValue.isValid() && this.picker) { this.dateControl.setValue(this.dateValue.format('YYYY-MM-DD'), { emitEvent: false }); this.picker.setMoment(this.dateValue); + } else { + this.dateControl.setValue(null, { emitEvent: false }); } this.suppressEvents = false;