diff --git a/src/Squidex.Events/Schemas/SchemaFieldsReordered.cs b/src/Squidex.Events/Schemas/SchemaFieldsReordered.cs new file mode 100644 index 000000000..0a89708e8 --- /dev/null +++ b/src/Squidex.Events/Schemas/SchemaFieldsReordered.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// SchemaFieldsReordered.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Infrastructure; + +namespace Squidex.Events.Schemas +{ + [TypeName("SchemaFieldsReorderedEvent")] + public class SchemaFieldsReordered : SchemaEvent + { + public List FieldIds { get; set; } + } +} diff --git a/src/Squidex.Events/Schemas/SchemaPublished.cs b/src/Squidex.Events/Schemas/SchemaPublished.cs index 77b49a043..052031a58 100644 --- a/src/Squidex.Events/Schemas/SchemaPublished.cs +++ b/src/Squidex.Events/Schemas/SchemaPublished.cs @@ -10,7 +10,7 @@ using Squidex.Infrastructure; namespace Squidex.Events.Schemas { - [TypeName("SchemaPublished")] + [TypeName("SchemaPublishedEvent")] public class SchemaPublished : SchemaEvent { } diff --git a/src/Squidex.Events/Schemas/SchemaUnpublished.cs b/src/Squidex.Events/Schemas/SchemaUnpublished.cs index d3dc1ac3d..f9682c4da 100644 --- a/src/Squidex.Events/Schemas/SchemaUnpublished.cs +++ b/src/Squidex.Events/Schemas/SchemaUnpublished.cs @@ -10,7 +10,7 @@ using Squidex.Infrastructure; namespace Squidex.Events.Schemas { - [TypeName("SchemaUnpublished")] + [TypeName("SchemaUnpublishedEvent")] public class SchemaUnpublished : SchemaEvent { } diff --git a/src/Squidex.Events/Schemas/Utils/SchemaEventDispatcher.cs b/src/Squidex.Events/Schemas/Utils/SchemaEventDispatcher.cs index c66e8caac..a08f724b8 100644 --- a/src/Squidex.Events/Schemas/Utils/SchemaEventDispatcher.cs +++ b/src/Squidex.Events/Schemas/Utils/SchemaEventDispatcher.cs @@ -54,6 +54,11 @@ namespace Squidex.Events.Schemas.Utils return schema.Update(@event.Properties); } + public static Schema Dispatch(SchemaFieldsReordered @event, Schema schema) + { + return schema.ReorderFields(@event.FieldIds); + } + public static Schema Dispatch(FieldDeleted @event, Schema schema) { return schema.DeleteField(@event.FieldId.Id); diff --git a/src/Squidex.Write/Schemas/Commands/ReorderFields.cs b/src/Squidex.Write/Schemas/Commands/ReorderFields.cs new file mode 100644 index 000000000..1128aa414 --- /dev/null +++ b/src/Squidex.Write/Schemas/Commands/ReorderFields.cs @@ -0,0 +1,26 @@ +// ========================================================================== +// ReorderFields.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using Squidex.Infrastructure; + +namespace Squidex.Write.Schemas.Commands +{ + public class ReorderFields : SchemaAggregateCommand, IValidatable + { + public List FieldIds { get; set; } + + public void Validate(IList errors) + { + if (FieldIds == null) + { + errors.Add(new ValidationError("Field ids must be specified", nameof(FieldIds))); + } + } + } +} diff --git a/src/Squidex.Write/Schemas/SchemaCommandHandler.cs b/src/Squidex.Write/Schemas/SchemaCommandHandler.cs index 8b5f0de87..b9757c423 100644 --- a/src/Squidex.Write/Schemas/SchemaCommandHandler.cs +++ b/src/Squidex.Write/Schemas/SchemaCommandHandler.cs @@ -90,6 +90,11 @@ namespace Squidex.Write.Schemas return handler.UpdateAsync(context, s => s.ShowField(command)); } + protected Task On(ReorderFields command, CommandContext context) + { + return handler.UpdateAsync(context, s => s.Reorder(command)); + } + protected Task On(UpdateSchema command, CommandContext context) { return handler.UpdateAsync(context, s => s.Update(command)); diff --git a/src/Squidex.Write/Schemas/SchemaDomainObject.cs b/src/Squidex.Write/Schemas/SchemaDomainObject.cs index 51279aec5..c55bb6141 100644 --- a/src/Squidex.Write/Schemas/SchemaDomainObject.cs +++ b/src/Squidex.Write/Schemas/SchemaDomainObject.cs @@ -91,6 +91,11 @@ namespace Squidex.Write.Schemas schema = SchemaEventDispatcher.Dispatch(@event, schema); } + protected void On(SchemaFieldsReordered @event) + { + schema = SchemaEventDispatcher.Dispatch(@event, schema); + } + protected void On(SchemaPublished @event) { schema = SchemaEventDispatcher.Dispatch(@event, schema); @@ -112,7 +117,7 @@ namespace Squidex.Write.Schemas VerifyCreatedAndNotDeleted(); - RaiseEvent(SimpleMapper.Map(command, new FieldAdded { FieldId = new NamedId(++totalFields, command.Name) })); + RaiseEvent(SimpleMapper.Map(command, new FieldAdded { FieldId = new NamedId(totalFields + 1, command.Name) })); return this; } @@ -139,6 +144,17 @@ namespace Squidex.Write.Schemas return this; } + public SchemaDomainObject Reorder(ReorderFields command) + { + Guard.Valid(command, nameof(command), () => $"Cannot reorder fields for schema '{Id}'"); + + VerifyCreatedAndNotDeleted(); + + RaiseEvent(SimpleMapper.Map(command, new SchemaFieldsReordered())); + + return this; + } + public SchemaDomainObject Update(UpdateSchema command) { Guard.Valid(command, nameof(command), () => $"Cannot update schema '{Id}'"); diff --git a/src/Squidex/Controllers/Api/Schemas/Models/ReorderFieldsDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/ReorderFieldsDto.cs new file mode 100644 index 000000000..b4e2ab89e --- /dev/null +++ b/src/Squidex/Controllers/Api/Schemas/Models/ReorderFieldsDto.cs @@ -0,0 +1,22 @@ +// ========================================================================== +// ReorderFieldsDto.cs +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex Group +// All rights reserved. +// ========================================================================== + +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Squidex.Controllers.Api.Schemas.Models +{ + public class ReorderFieldsDto + { + /// + /// The field ids in the target order. + /// + [Required] + public List FieldIds { get; set; } + } +} diff --git a/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs b/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs index c44c6ba69..55e7c1b28 100644 --- a/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs +++ b/src/Squidex/Controllers/Api/Schemas/SchemaFieldsController.cs @@ -40,9 +40,9 @@ namespace Squidex.Controllers.Api.Schemas /// The field object that needs to be added to the schema. /// /// 201 => Schema field created. - /// 409 => Schema field name already in use. - /// 404 => App or schema not found. /// 400 => Schema field properties not valid. + /// 404 => Schema or app not found. + /// 409 => Schema field name already in use. /// [HttpPost] [Route("apps/{app}/schemas/{name}/fields/")] @@ -61,6 +61,29 @@ namespace Squidex.Controllers.Api.Schemas return StatusCode(201, response); } + /// + /// Reorders the fields. + /// + /// The name of the app. + /// The name of the schema. + /// The request that contains the field ids. + /// + /// 204 => Schema fields reorderd. + /// 400 => Schema field ids do not cover the fields of the schema. + /// 404 => Schema or app not found. + /// + [HttpPost] + [Route("apps/{app}/schemas/{name}/fields/ordering")] + [ProducesResponseType(typeof(ErrorDto), 400)] + public async Task PutFieldOrdering(string app, string name, [FromBody] ReorderFields request) + { + var command = new ReorderFields { FieldIds = request.FieldIds }; + + await CommandBus.PublishAsync(command); + + return NoContent(); + } + /// /// Update a schema field. /// @@ -69,10 +92,9 @@ namespace Squidex.Controllers.Api.Schemas /// The id of the field to update. /// The field object that needs to be added to the schema. /// - /// 204 => Schema field created. - /// 409 => Schema field name already in use. - /// 404 => App, schema or field not found. + /// 204 => Schema field updated. /// 400 => Schema field properties not valid. + /// 404 => Schema, field or app not found. /// [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{id:long}/")] @@ -94,9 +116,9 @@ namespace Squidex.Controllers.Api.Schemas /// The name of the schema. /// The id of the field to hide. /// - /// 400 => Schema field already hidden. /// 204 => Schema field hidden. - /// 404 => App, schema or field not found. + /// 400 => Schema field already hidden. + /// 404 => Schema, field or app not found. /// /// /// A hidden field is not part of the API response, but can still be edited in the portal. @@ -120,9 +142,9 @@ namespace Squidex.Controllers.Api.Schemas /// The name of the schema. /// The id of the field to shows. /// - /// 400 => Schema field already visible. /// 204 => Schema field shown. - /// 404 => App, schema or field not found. + /// 400 => Schema field already visible. + /// 404 => Schema, field or app not found. /// /// /// A hidden field is not part of the API response, but can still be edited in the portal. @@ -146,9 +168,9 @@ namespace Squidex.Controllers.Api.Schemas /// The name of the schema. /// The id of the field to enable. /// - /// 400 => Schema field already enabled. /// 204 => Schema field enabled. - /// 404 => App, schema or field not found. + /// 400 => Schema field already enabled. + /// 404 => Schema, field or app not found. /// /// /// A disabled field cannot not be edited in the squidex portal anymore, @@ -173,9 +195,9 @@ namespace Squidex.Controllers.Api.Schemas /// The name of the schema. /// The id of the field to disable. /// - /// 400 => Schema field already disabled. /// 204 => Schema field disabled. - /// 404 => App, schema or field not found. + /// 400 => Schema field already disabled. + /// 404 => Schema, field or app not found. /// /// /// A disabled field cannot not be edited in the squidex portal anymore, @@ -201,7 +223,7 @@ namespace Squidex.Controllers.Api.Schemas /// The id of the field to disable. /// /// 204 => Schema field deleted. - /// 404 => App, schema or field not found. + /// 404 => Schema, field or app not found. /// [HttpDelete] [Route("apps/{app}/schemas/{name}/fields/{id:long}/")] diff --git a/src/Squidex/Controllers/Api/Schemas/SchemasController.cs b/src/Squidex/Controllers/Api/Schemas/SchemasController.cs index a1e933331..cf2266fb8 100644 --- a/src/Squidex/Controllers/Api/Schemas/SchemasController.cs +++ b/src/Squidex/Controllers/Api/Schemas/SchemasController.cs @@ -46,6 +46,7 @@ namespace Squidex.Controllers.Api.Schemas /// /// /// 200 => Schemas returned. + /// 404 => App not found. /// [HttpGet] [Route("apps/{app}/schemas/")] @@ -65,7 +66,7 @@ namespace Squidex.Controllers.Api.Schemas /// The name of the schema to retrieve. /// /// 200 => Schema found. - /// 404 => Schema not found. + /// 404 => Schema or app not found. /// [HttpGet] [Route("apps/{app}/schemas/{name}/")] @@ -93,7 +94,7 @@ namespace Squidex.Controllers.Api.Schemas /// The name of the app to create the schema to. /// /// 201 => Schema created. - /// 400 => Schema name is not valid. + /// 400 => Schema name or properties are not valid. /// 409 => Schema name already in use. /// [HttpPost] @@ -117,7 +118,9 @@ namespace Squidex.Controllers.Api.Schemas /// The name of the schema. /// The schema object that needs to updated. /// - /// 204 => Schema updated. + /// 204 => Schema has been updated. + /// 400 => Schema properties are not valid. + /// 404 => Schema or app not found. /// [HttpPut] [Route("apps/{app}/schemas/{name}/")] @@ -136,8 +139,9 @@ namespace Squidex.Controllers.Api.Schemas /// The app where the schema is a part of. /// The name of the schema to publish. /// + /// 204 => Schema has been published. /// 400 => Schema is already published. - /// 204 => Schema has been deleted. + /// 404 => Schema or app not found. /// [HttpPut] [Route("apps/{app}/schemas/{name}/publish")] @@ -155,8 +159,9 @@ namespace Squidex.Controllers.Api.Schemas /// The app where the schema is a part of. /// The name of the schema to unpublish. /// + /// 204 => Schema has been unpublished. /// 400 => Schema is not published. - /// 204 => Schema has been deleted. + /// 404 => Schema or app not found. /// [HttpPut] [Route("apps/{app}/schemas/{name}/unpublish")] @@ -175,6 +180,7 @@ namespace Squidex.Controllers.Api.Schemas /// The name of the schema to delete. /// /// 204 => Schema has been deleted. + /// 404 => Schema or app not found. /// [HttpDelete] [Route("apps/{app}/schemas/{name}/")] diff --git a/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs b/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs index 80cd0b6a5..6cfe3fff9 100644 --- a/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs +++ b/tests/Squidex.Write.Tests/Schemas/SchemaCommandHandlerTests.cs @@ -7,6 +7,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Threading.Tasks; using Moq; using Squidex.Core.Schemas; @@ -84,6 +85,19 @@ namespace Squidex.Write.Schemas }); } + [Fact] + public async Task ReorderSchema_should_update_domain_object() + { + CreateSchema(); + + var context = CreateContextForCommand(new ReorderFields { FieldIds = new List() }); + + await TestUpdate(schema, async _ => + { + await sut.HandleAsync(context); + }); + } + [Fact] public async Task PublishSchema_should_update_domain_object() { diff --git a/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs b/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs index 14fbf43e0..69c8e3f9f 100644 --- a/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs +++ b/tests/Squidex.Write.Tests/Schemas/SchemaDomainObjectTests.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using System.Collections.Generic; using System.Linq; using Squidex.Core.Schemas; using Squidex.Events.Schemas; @@ -68,6 +69,7 @@ namespace Squidex.Write.Schemas CreateEvent(new SchemaCreated { Name = SchemaName, Properties = properties }) ); } + [Fact] public void Update_should_throw_if_not_created() { @@ -83,9 +85,9 @@ namespace Squidex.Write.Schemas CreateSchema(); DeleteSchema(); - Assert.Throws(() => + Assert.Throws(() => { - sut.Update(CreateCommand(new UpdateSchema())); + sut.Update(CreateCommand(new UpdateSchema { Properties = new SchemaProperties() })); }); } @@ -117,6 +119,58 @@ namespace Squidex.Write.Schemas ); } + [Fact] + public void Reorder_should_throw_if_not_created() + { + Assert.Throws(() => + { + sut.Reorder(CreateCommand(new ReorderFields { FieldIds = new List() })); + }); + } + + [Fact] + public void Reorder_should_throw_if_schema_is_deleted() + { + CreateSchema(); + DeleteSchema(); + + Assert.Throws(() => + { + sut.Reorder(CreateCommand(new ReorderFields { FieldIds = new List() })); + }); + } + + [Fact] + public void Reorder_should_throw_if_command_is_not_valid() + { + CreateSchema(); + + Assert.Throws(() => + { + sut.Reorder(CreateCommand(new ReorderFields())); + }); + } + + [Fact] + public void Reorder_should_refresh_properties_and_create_events() + { + var fieldIds = new List { 1, 2 }; + + CreateSchema(); + + sut.AddField(new AddField { Name = "field1", Properties = new StringFieldProperties() }); + sut.AddField(new AddField { Name = "field2", Properties = new StringFieldProperties() }); + + ((IAggregate)sut).ClearUncommittedEvents(); + + sut.Reorder(CreateCommand(new ReorderFields { FieldIds = fieldIds })); + + sut.GetUncomittedEvents() + .ShouldHaveSameEvents( + CreateEvent(new SchemaFieldsReordered { FieldIds = fieldIds }) + ); + } + [Fact] public void Publish_should_throw_if_not_created() {