From 84474dfb2f0dee4b346c464fca62c2c1bd80d60c Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 15 Jun 2019 16:13:50 +0200 Subject: [PATCH] Update for hateos in UI --- .../Schemas/Guards/GuardHelper.cs | 24 +++++++-- .../Schemas/Guards/GuardSchema.cs | 2 +- .../Schemas/Guards/GuardSchemaField.cs | 48 +++++++---------- src/Squidex.Shared/Permissions.cs | 3 -- .../Controllers/Backups/RestoreController.cs | 4 +- .../Controllers/Schemas/Models/FieldDto.cs | 53 ++++++++++++++++++- .../Schemas/Models/NestedFieldDto.cs | 37 ++++++++++++- .../Schemas/Models/SchemaDetailsDto.cs | 20 ++++++- .../Controllers/Schemas/Models/SchemaDto.cs | 42 +++++++++++++-- .../Controllers/Schemas/Models/SchemasDto.cs | 10 ++++ .../Controllers/Schemas/SchemasController.cs | 2 +- .../Controllers/Users/Models/ResourcesDto.cs | 8 +-- .../pages/users/user-page.component.ts | 4 +- .../pages/schemas/schemas-page.component.html | 2 +- .../pages/rules/rule-wizard.component.ts | 4 +- .../schemas/pages/schema/field.component.html | 6 ++- .../pages/schema/schema-page.component.html | 18 ++++--- .../pages/schemas/schemas-page.component.html | 14 +++-- .../pages/languages/language.component.html | 5 +- .../app/framework/angular/sorted.directive.ts | 30 +++++------ src/Squidex/app/framework/utils/hateos.ts | 2 +- .../components/asset-dialog.component.ts | 4 +- .../app/shared/components/asset.component.ts | 4 +- .../components/schema-category.component.html | 44 +++++++-------- .../components/schema-category.component.ts | 9 ++-- .../shared/services/schemas.service.spec.ts | 4 +- .../app/shared/services/schemas.service.ts | 42 ++++++++------- .../ApiPermissionAttributeTests.cs | 8 +-- 28 files changed, 312 insertions(+), 141 deletions(-) diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardHelper.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardHelper.cs index b0a2b8d5d..760a6086e 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardHelper.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardHelper.cs @@ -12,21 +12,26 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { public static class GuardHelper { - public static IArrayField GetArrayFieldOrThrow(Schema schema, long parentId) + public static IArrayField GetArrayFieldOrThrow(Schema schema, long parentId, bool allowLocked) { if (!schema.FieldsById.TryGetValue(parentId, out var rootField) || !(rootField is IArrayField arrayField)) { throw new DomainObjectNotFoundException(parentId.ToString(), "Fields", typeof(Schema)); } + if (!allowLocked) + { + EnsureNotLocked(arrayField); + } + return arrayField; } - public static IField GetFieldOrThrow(Schema schema, long fieldId, long? parentId) + public static IField GetFieldOrThrow(Schema schema, long fieldId, long? parentId, bool allowLocked) { if (parentId.HasValue) { - var arrayField = GetArrayFieldOrThrow(schema, parentId.Value); + var arrayField = GetArrayFieldOrThrow(schema, parentId.Value, allowLocked); if (!arrayField.FieldsById.TryGetValue(fieldId, out var nestedField)) { @@ -41,7 +46,20 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards throw new DomainObjectNotFoundException(fieldId.ToString(), "Fields", typeof(Schema)); } + if (!allowLocked) + { + EnsureNotLocked(field); + } + return field; } + + private static void EnsureNotLocked(IField field) + { + if (field.IsLocked) + { + throw new DomainException("Schema field is locked."); + } + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs index 4e937c96e..1a87bdf21 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs @@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (command.ParentFieldId.HasValue) { - arrayField = GuardHelper.GetArrayFieldOrThrow(schema, command.ParentFieldId.Value); + arrayField = GuardHelper.GetArrayFieldOrThrow(schema, command.ParentFieldId.Value, false); } Validate.It(() => "Cannot reorder schema fields.", error => diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs index ed93453bc..93b152d8c 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs @@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (command.ParentFieldId.HasValue) { - var arrayField = GuardHelper.GetArrayFieldOrThrow(schema, command.ParentFieldId.Value); + var arrayField = GuardHelper.GetArrayFieldOrThrow(schema, command.ParentFieldId.Value, false); if (arrayField.FieldsByName.ContainsKey(command.Name)) { @@ -64,12 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { Guard.NotNull(command, nameof(command)); - var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId); - - if (field.IsLocked) - { - throw new DomainException("Schema field is already locked."); - } + var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId, false); Validate.It(() => "Cannot update field.", e => { @@ -90,12 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { Guard.NotNull(command, nameof(command)); - var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId); - - if (field.IsLocked) - { - throw new DomainException("Schema field is locked."); - } + var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId, false); if (field.IsHidden) { @@ -108,11 +98,23 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards } } + public static void CanShow(Schema schema, ShowField command) + { + Guard.NotNull(command, nameof(command)); + + var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId, false); + + if (!field.IsHidden) + { + throw new DomainException("Schema field is already visible."); + } + } + public static void CanDisable(Schema schema, DisableField command) { Guard.NotNull(command, nameof(command)); - var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId); + var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId, false); if (field.IsDisabled) { @@ -129,7 +131,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { Guard.NotNull(command, nameof(command)); - var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId); + var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId, false); if (field.IsLocked) { @@ -137,23 +139,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards } } - public static void CanShow(Schema schema, ShowField command) - { - Guard.NotNull(command, nameof(command)); - - var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId); - - if (!field.IsHidden) - { - throw new DomainException("Schema field is already visible."); - } - } - public static void CanEnable(Schema schema, EnableField command) { Guard.NotNull(command, nameof(command)); - var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId); + var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId, false); if (!field.IsDisabled) { @@ -165,7 +155,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { Guard.NotNull(command, nameof(command)); - var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId); + var field = GuardHelper.GetFieldOrThrow(schema, command.FieldId, command.ParentFieldId, false); if (field.IsLocked) { diff --git a/src/Squidex.Shared/Permissions.cs b/src/Squidex.Shared/Permissions.cs index ee1e20cf4..98cb299c0 100644 --- a/src/Squidex.Shared/Permissions.cs +++ b/src/Squidex.Shared/Permissions.cs @@ -37,8 +37,6 @@ namespace Squidex.Shared public const string AdminAppCreate = "squidex.admin.apps.create"; public const string AdminRestore = "squidex.admin.restore"; - public const string AdminRestoreRead = "squidex.admin.restore.read"; - public const string AdminRestoreCreate = "squidex.admin.restore.create"; public const string AdminEvents = "squidex.admin.events"; public const string AdminEventsRead = "squidex.admin.events.read"; @@ -107,7 +105,6 @@ namespace Squidex.Shared public const string AppRulesDelete = "squidex.apps.{app}.rules.delete"; public const string AppSchemas = "squidex.apps.{app}.schemas.{name}"; - public const string AppSchemasRead = "squidex.apps.{app}.schemas.{name}.read"; public const string AppSchemasCreate = "squidex.apps.{app}.schemas.{name}.create"; public const string AppSchemasUpdate = "squidex.apps.{app}.schemas.{name}.update"; public const string AppSchemasScripts = "squidex.apps.{app}.schemas.{name}.scripts"; diff --git a/src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs b/src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs index 6ad1cf1c0..4426330de 100644 --- a/src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs +++ b/src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs @@ -41,7 +41,7 @@ namespace Squidex.Areas.Api.Controllers.Backups [HttpGet] [Route("apps/restore/")] [ProducesResponseType(typeof(RestoreJobDto), 200)] - [ApiPermission(Permissions.AdminRestoreRead)] + [ApiPermission(Permissions.AdminRestore)] public async Task GetJob() { var restoreGrain = grainFactory.GetGrain(SingleGrain.Id); @@ -67,7 +67,7 @@ namespace Squidex.Areas.Api.Controllers.Backups /// [HttpPost] [Route("apps/restore/")] - [ApiPermission(Permissions.AdminRestoreCreate)] + [ApiPermission(Permissions.AdminRestore)] public async Task PostRestore([FromBody] RestoreRequest request) { var restoreGrain = grainFactory.GetGrain(SingleGrain.Id); diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs index d9eab980f..97ad65125 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs @@ -7,10 +7,12 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Squidex.Areas.Api.Controllers.Schemas.Models.Fields; +using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - public sealed class FieldDto + public sealed class FieldDto : Resource { /// /// The id of the field. @@ -55,5 +57,54 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// The nested fields. /// public List Nested { get; set; } + + public void CreateLinks(ApiController controller, string app, string schema, bool allowUpdate) + { + allowUpdate = allowUpdate && !IsLocked; + + if (allowUpdate) + { + var values = new { app, name = schema, id = FieldId }; + + AddPutLink("update", controller.Url(x => nameof(x.PutField), values)); + + if (IsHidden) + { + AddPutLink("show", controller.Url(x => nameof(x.ShowField), values)); + } + else + { + AddPutLink("hide", controller.Url(x => nameof(x.HideField), values)); + } + + if (IsDisabled) + { + AddPutLink("enable", controller.Url(x => nameof(x.EnableField), values)); + } + else + { + AddPutLink("show", controller.Url(x => nameof(x.DisableField), values)); + } + + if (Properties is ArrayFieldPropertiesDto) + { + AddPostLink("fields/add", controller.Url(x => nameof(x.PostNestedField), values)); + + AddPutLink("order", controller.Url(x => nameof(x.PutNestedFieldOrdering), values)); + } + + AddPutLink("lock", controller.Url(x => nameof(x.LockField), values)); + + AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteField), values)); + } + + if (Nested != null) + { + foreach (var nested in Nested) + { + nested.CreateLinks(controller, app, schema, FieldId, allowUpdate); + } + } + } } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/NestedFieldDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/NestedFieldDto.cs index 334c50376..580ea0220 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/NestedFieldDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/NestedFieldDto.cs @@ -6,10 +6,11 @@ // ========================================================================== using System.ComponentModel.DataAnnotations; +using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models { - public sealed class NestedFieldDto + public sealed class NestedFieldDto : Resource { /// /// The id of the field. @@ -43,5 +44,39 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// [Required] public FieldPropertiesDto Properties { get; set; } + + public void CreateLinks(ApiController controller, string app, string schema, long parentId, bool allowUpdate) + { + allowUpdate = allowUpdate && !IsLocked; + + if (allowUpdate) + { + var values = new { app, name = schema, parentId, id = FieldId }; + + AddPutLink("update", controller.Url(x => nameof(x.PutNestedField), values)); + + if (IsHidden) + { + AddPutLink("show", controller.Url(x => nameof(x.ShowNestedField), values)); + } + else + { + AddPutLink("hide", controller.Url(x => nameof(x.HideNestedField), values)); + } + + if (IsDisabled) + { + AddPutLink("enable", controller.Url(x => nameof(x.EnableNestedField), values)); + } + else + { + AddPutLink("show", controller.Url(x => nameof(x.DisableNestedField), values)); + } + + AddPutLink("lock", controller.Url(x => nameof(x.LockNestedField), values)); + + AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteNestedField), values)); + } + } } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs index b71e85280..716dc5ad0 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs @@ -11,6 +11,7 @@ using Squidex.Areas.Api.Controllers.Schemas.Models.Converters; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure.Reflection; +using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models @@ -85,7 +86,24 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models result.Fields.Add(fieldDto); } - return (SchemaDetailsDto)result.CreateLinks(controller, app); + result.CreateLinks(controller, app); + + return result; + } + + protected override void CreateLinks(ApiController controller, string app) + { + base.CreateLinks(controller, app); + + var allowUpdate = controller.HasPermission(Permissions.AppSchemasUpdate, app, Name); + + if (Fields != null) + { + foreach (var nested in Fields) + { + nested.CreateLinks(controller, app, Name, allowUpdate); + } + } } } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs index 7ae0addad..7a48b73d9 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs @@ -87,13 +87,17 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models SimpleMapper.Map(schema.SchemaDef, result); SimpleMapper.Map(schema.SchemaDef.Properties, result.Properties); - return result.CreateLinks(controller, app); + result.CreateLinks(controller, app); + + return result; } - protected virtual SchemaDto CreateLinks(ApiController controller, string app) + protected virtual void CreateLinks(ApiController controller, string app) { var values = new { app, name = Name }; + var allowUpdate = controller.HasPermission(Permissions.AppSchemasUpdate, app, Name); + AddSelfLink(controller.Url(x => nameof(x.GetSchema), values)); if (controller.HasPermission(Permissions.AppContentsRead, app, Name)) @@ -101,7 +105,39 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models AddGetLink("contents", controller.Url(x => nameof(x.GetContents), values)); } - return this; + if (controller.HasPermission(Permissions.AppSchemasPublish, app, Name)) + { + if (IsPublished) + { + AddPutLink("unpublish", controller.Url(x => nameof(x.UnpublishSchema), values)); + } + else + { + AddPutLink("publish", controller.Url(x => nameof(x.PublishSchema), values)); + } + } + + if (allowUpdate) + { + AddPutLink("order", controller.Url(x => nameof(x.PutSchemaFieldOrdering), values)); + + AddPutLink("update", controller.Url(x => nameof(x.PutSchema), values)); + AddPutLink("update/category", controller.Url(x => nameof(x.PutCategory), values)); + AddPutLink("update/sync", controller.Url(x => nameof(x.PutSchemaSync), values)); + AddPutLink("update/urls", controller.Url(x => nameof(x.PutPreviewUrls), values)); + + AddPostLink("fields/add", controller.Url(x => nameof(x.PostField), values)); + } + + if (controller.HasPermission(Permissions.AppSchemasScripts, app, Name)) + { + AddPutLink("update/scripts", controller.Url(x => nameof(x.PutScripts), values)); + } + + if (controller.HasPermission(Permissions.AppSchemasDelete, app, Name)) + { + AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteSchema), values)); + } } } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs index 8fb9755fa..596c80d07 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; +using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Schemas.Models @@ -37,6 +38,15 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models private SchemasDto CreateLinks(ApiController controller, string app) { + var values = new { app }; + + AddSelfLink(controller.Url(x => nameof(x.GetSchemas), values)); + + if (controller.HasPermission(Permissions.AppSchemasCreate, app)) + { + AddPostLink("create", controller.Url(x => nameof(x.PostSchema), values)); + } + return this; } } diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index 854b24f57..331793b5e 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -242,7 +242,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasScripts)] [ApiCosts(1)] - public async Task PutSchemaScripts(string app, string name, [FromBody] SchemaScriptsDto request) + public async Task PutScripts(string app, string name, [FromBody] SchemaScriptsDto request) { var command = request.ToCommand(); diff --git a/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs b/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs index 885b4fba4..0b5fa32b2 100644 --- a/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs @@ -22,14 +22,12 @@ namespace Squidex.Areas.Api.Controllers.Users.Models result.AddGetLink("ping", controller.Url(x => nameof(x.GetPing))); - result.AddGetLink("languages", controller.Url(x => nameof(x.GetLanguages))); - if (controller.HasPermission(Permissions.AdminEventsRead)) { - result.AddGetLink("admin/eventConsumers", controller.Url(x => nameof(x.GetEventConsumers))); + result.AddGetLink("admin/events", controller.Url(x => nameof(x.GetEventConsumers))); } - if (controller.HasPermission(Permissions.AdminRestoreRead)) + if (controller.HasPermission(Permissions.AdminRestore)) { result.AddGetLink("admin/restore", controller.Url(x => nameof(x.GetJob))); } @@ -39,6 +37,8 @@ namespace Squidex.Areas.Api.Controllers.Users.Models result.AddGetLink("admin/users", controller.Url(x => nameof(x.GetUsers))); } + result.AddGetLink("languages", controller.Url(x => nameof(x.GetLanguages))); + return result; } } diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.ts b/src/Squidex/app/features/administration/pages/users/user-page.component.ts index 069c71789..83b2cef3d 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.ts +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.ts @@ -9,7 +9,7 @@ import { Component, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import { hasLink, ResourceOwner } from '@app/shared'; +import { hasAnyLink, ResourceOwner } from '@app/shared'; import { CreateUserDto, @@ -47,7 +47,7 @@ export class UserPageComponent extends ResourceOwner implements OnInit { if (selectedUser) { this.userForm.load(selectedUser); - this.isEditable = hasLink(this.user, 'update'); + this.isEditable = hasAnyLink(this.user, 'update'); if (!this.isEditable) { this.userForm.form.disable(); diff --git a/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html b/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html index a270463cd..ec3ffec5c 100644 --- a/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html +++ b/src/Squidex/app/features/content/pages/schemas/schemas-page.component.html @@ -22,7 +22,7 @@ [schemas]="schemas" [schemasFilter]="schemasFilter.valueChanges | async" [routeSingletonToContent]="true" - [isReadonly]="true"> + [forContent]="true"> diff --git a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts index a7e1e14a4..7b7252549 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts @@ -10,7 +10,7 @@ import { FormGroup } from '@angular/forms'; import { Form, - hasLink, + hasAnyLink, ImmutableArray, RuleDto, RuleElementDto, @@ -64,7 +64,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit { } public ngOnInit() { - this.isEditable = !this.rule || hasLink(this.rule, 'update'); + this.isEditable = !this.rule || hasAnyLink(this.rule, 'update'); if (this.mode === MODE_EDIT_ACTION) { this.step = 4; diff --git a/src/Squidex/app/features/schemas/pages/schema/field.component.html b/src/Squidex/app/features/schemas/pages/schema/field.component.html index 1858af5e6..b07a71279 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/field.component.html @@ -81,6 +81,7 @@ [patterns]="patterns" [editForm]="editForm.form" [editFormSubmitted]="editForm.submitted | async" + [isEditable]="isEditable" [field]="field"> @@ -90,7 +91,10 @@ -
+
diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html index 3fe3ea41b..8a76a5c1c 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html @@ -31,11 +31,13 @@ Edit Preview Urls - - - - Clone - + + + + + Clone + + @@ -63,14 +65,14 @@
No field created yet. -
@@ -78,7 +80,7 @@
-
diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html index a1b82746f..d3305eeb2 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html +++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html @@ -6,12 +6,18 @@
- - + + + + +
diff --git a/src/Squidex/app/features/settings/pages/languages/language.component.html b/src/Squidex/app/features/settings/pages/languages/language.component.html index b8deb6e3c..a446ca2c1 100644 --- a/src/Squidex/app/features/settings/pages/languages/language.component.html +++ b/src/Squidex/app/features/settings/pages/languages/language.component.html @@ -39,7 +39,10 @@
-
+
diff --git a/src/Squidex/app/framework/angular/sorted.directive.ts b/src/Squidex/app/framework/angular/sorted.directive.ts index 3629eb1f7..d02c77797 100644 --- a/src/Squidex/app/framework/angular/sorted.directive.ts +++ b/src/Squidex/app/framework/angular/sorted.directive.ts @@ -5,16 +5,17 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { Directive, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core'; import * as Sortable from 'sortablejs'; +const DEFAULT_PROPS = { sort: true, animation: 150 }; + @Directive({ selector: '[sqxSortModel]' }) -export class SortedDirective implements OnDestroy, OnInit { +export class SortedDirective implements OnChanges, OnDestroy, OnInit { private sortable: Sortable.Ref; - private isDisabled: boolean; @Input() public dragHandle = '.drag-handle'; @@ -25,20 +26,20 @@ export class SortedDirective implements OnDestroy, OnInit { @Output('sqxSort') public sort = new EventEmitter(); - @Input('disabled') - public setDisabled(value: boolean) { - this.isDisabled = value; - - if (this.sortable) { - this.sortable.option('disabled', value); - } - } + @Input('sqxSortDisabled') + public isDisabled = false; constructor( private readonly elementRef: ElementRef ) { } + public ngOnChanges() { + if (this.sortable) { + this.sortable.option('disabled', this.isDisabled); + } + } + public ngOnDestroy() { if (this.sortable) { this.sortable.destroy(); @@ -47,8 +48,7 @@ export class SortedDirective implements OnDestroy, OnInit { public ngOnInit() { this.sortable = Sortable.create(this.elementRef.nativeElement, { - sort: true, - animation: 150, + ...DEFAULT_PROPS, onSort: (event: { oldIndex: number, newIndex: number }) => { if (this.sortModel && event.newIndex !== event.oldIndex) { @@ -63,9 +63,9 @@ export class SortedDirective implements OnDestroy, OnInit { } }, - isDisabled: this.isDisabled, - handle: this.dragHandle }); + + this.sortable.option('disabled', this.isDisabled); } } \ No newline at end of file diff --git a/src/Squidex/app/framework/utils/hateos.ts b/src/Squidex/app/framework/utils/hateos.ts index ed1e3f9a0..e459c0dc8 100644 --- a/src/Squidex/app/framework/utils/hateos.ts +++ b/src/Squidex/app/framework/utils/hateos.ts @@ -48,7 +48,7 @@ export function withLinks(value: T, source: Resource) { return value; } -export function hasLink(value: Resource | ResourceLinks, rel: string): boolean { +function hasLink(value: Resource | ResourceLinks, rel: string): boolean { const link = getLink(value, rel); return !!(link && link.method && link.href); diff --git a/src/Squidex/app/shared/components/asset-dialog.component.ts b/src/Squidex/app/shared/components/asset-dialog.component.ts index e5d14c0db..729173dfe 100644 --- a/src/Squidex/app/shared/components/asset-dialog.component.ts +++ b/src/Squidex/app/shared/components/asset-dialog.component.ts @@ -13,7 +13,7 @@ import { AppsState, AssetDto, AssetsService, - hasLink, + hasAnyLink, StatefulComponent } from '@app/shared/internal'; @@ -55,7 +55,7 @@ export class AssetDialogComponent extends StatefulComponent implements OnInit { public ngOnInit() { this.annotateForm.load(this.asset); - this.isReadOnly = !hasLink(this.asset, 'update'); + this.isReadOnly = !hasAnyLink(this.asset, 'update'); if (this.isReadOnly) { this.annotateForm.form.disable(); diff --git a/src/Squidex/app/shared/components/asset.component.ts b/src/Squidex/app/shared/components/asset.component.ts index aab3ee5c5..3c697f38b 100644 --- a/src/Squidex/app/shared/components/asset.component.ts +++ b/src/Squidex/app/shared/components/asset.component.ts @@ -13,7 +13,7 @@ import { DialogModel, DialogService, fadeAnimation, - hasLink, + hasAnyLink, StatefulComponent, Types, UploadCanceled @@ -113,7 +113,7 @@ export class AssetComponent extends StatefulComponent implements OnInit { } public updateFile(files: FileList) { - if (files.length === 1 && hasLink(this.asset, 'upload')) { + if (files.length === 1 && hasAnyLink(this.asset, 'upload')) { this.setProgress(1); this.assetUploader.uploadAsset(this.asset, files[0]) diff --git a/src/Squidex/app/shared/components/schema-category.component.html b/src/Squidex/app/shared/components/schema-category.component.html index 12025b426..67e1fe40d 100644 --- a/src/Squidex/app/shared/components/schema-category.component.html +++ b/src/Squidex/app/shared/components/schema-category.component.html @@ -15,29 +15,29 @@
+ + + {{schema.displayName}} + + +
diff --git a/src/Squidex/app/shared/components/schema-category.component.ts b/src/Squidex/app/shared/components/schema-category.component.ts index 569d37749..67e2ac57d 100644 --- a/src/Squidex/app/shared/components/schema-category.component.ts +++ b/src/Squidex/app/shared/components/schema-category.component.ts @@ -9,6 +9,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, In import { fadeAnimation, + hasAnyLink, ImmutableArray, LocalStoreService, SchemaDetailsDto, @@ -41,7 +42,7 @@ export class SchemaCategoryComponent extends StatefulComponent implements public name: string; @Input() - public isReadonly: boolean; + public forContent: boolean; @Input() public routeSingletonToContent = false; @@ -122,7 +123,7 @@ export class SchemaCategoryComponent extends StatefulComponent implements } private isSameCategory(schema: SchemaDto): boolean { - return (!this.name && !schema.category) || schema.category === this.name; + return ((!this.name && !schema.category) || schema.category === this.name) && (!this.forContent || hasAnyLink(schema, 'contents')); } public changeCategory(schema: SchemaDto) { @@ -137,10 +138,6 @@ export class SchemaCategoryComponent extends StatefulComponent implements return schema.id; } - public schemaPermission(schema: SchemaDto) { - return `?squidex.apps.{app}.schemas.${schema.name}.*;squidex.apps.{app}.contents.${schema.name}.*`; - } - private configKey(): string { return `squidex.schema.category.${this.name}.closed`; } diff --git a/src/Squidex/app/shared/services/schemas.service.spec.ts b/src/Squidex/app/shared/services/schemas.service.spec.ts index 94d094e2f..aecec8329 100644 --- a/src/Squidex/app/shared/services/schemas.service.spec.ts +++ b/src/Squidex/app/shared/services/schemas.service.spec.ts @@ -167,7 +167,7 @@ describe('SchemasService', () => { const resource: Resource = { _links: { - updateScripts: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/scripts' } + ['update/scripts']: { method: 'PUT', href: '/api/apps/my-app/schemas/my-schema/scripts' } } }; @@ -260,7 +260,7 @@ describe('SchemasService', () => { const resource: Resource = { _links: { - addField: { method: 'POST', href: '/api/apps/my-app/schemas/my-schema/fields' } + ['fields/add']: { method: 'POST', href: '/api/apps/my-app/schemas/my-schema/fields' } } }; diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index 4b40c16db..235171557 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -250,7 +250,7 @@ export class SchemasService { } public putScripts(appName: string, resource: Resource, dto: {}, version: Version): Observable { - const link = resource._links['updateScripts']; + const link = resource._links['update/scripts']; const url = this.apiUrl.buildUrl(link.href); @@ -340,7 +340,7 @@ export class SchemasService { } public postField(appName: string, resource: Resource, dto: AddFieldDto, version: Version): Observable { - const link = resource._links['addField']; + const link = resource._links['fields/add']; const url = this.apiUrl.buildUrl(link.href); @@ -503,26 +503,30 @@ function parseSchemaWithDetails(response: any, version: Version) { nestedItem.properties.fieldType, nestedItem.properties); - return new NestedFieldDto( - nestedItem.fieldId, - nestedItem.name, - nestedPropertiesDto, - item.fieldId, - nestedItem.isLocked, - nestedItem.isHidden, - nestedItem.isDisabled); + return withLinks( + new NestedFieldDto( + nestedItem.fieldId, + nestedItem.name, + nestedPropertiesDto, + item.fieldId, + nestedItem.isLocked, + nestedItem.isHidden, + nestedItem.isDisabled), + nestedItem); }); } - return new RootFieldDto( - item.fieldId, - item.name, - propertiesDto, - item.partitioning, - item.isLocked, - item.isHidden, - item.isDisabled, - nested || []); + return withLinks( + new RootFieldDto( + item.fieldId, + item.name, + propertiesDto, + item.partitioning, + item.isLocked, + item.isHidden, + item.isDisabled, + nested || []), + item); }); const properties = new SchemaPropertiesDto(response.properties.label, response.properties.hints); diff --git a/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs b/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs index 07297b7a6..9b95902d9 100644 --- a/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs +++ b/tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs @@ -63,7 +63,7 @@ namespace Squidex.Web user.AddClaim(new Claim(SquidexClaimTypes.Permissions, "squidex.apps.my-app")); - var sut = new ApiPermissionAttribute(Permissions.AppSchemasRead); + var sut = new ApiPermissionAttribute(Permissions.AppSchemasCreate); await sut.OnActionExecutionAsync(actionExecutingContext, next); @@ -78,7 +78,7 @@ namespace Squidex.Web user.AddClaim(new Claim(SquidexClaimTypes.Permissions, "squidex.apps.other-app")); - var sut = new ApiPermissionAttribute(Permissions.AppSchemasRead); + var sut = new ApiPermissionAttribute(Permissions.AppSchemasCreate); await sut.OnActionExecutionAsync(actionExecutingContext, next); @@ -91,7 +91,7 @@ namespace Squidex.Web { user.AddClaim(new Claim(SquidexClaimTypes.Permissions, "squidex.apps.other-app")); - var sut = new ApiPermissionAttribute(Permissions.AppSchemasRead); + var sut = new ApiPermissionAttribute(Permissions.AppSchemasCreate); await sut.OnActionExecutionAsync(actionExecutingContext, next); @@ -102,7 +102,7 @@ namespace Squidex.Web [Fact] public async Task Should_return_forbidden_when_user_has_no_permission() { - var sut = new ApiPermissionAttribute(Permissions.AppSchemasRead); + var sut = new ApiPermissionAttribute(Permissions.AppSchemasCreate); await sut.OnActionExecutionAsync(actionExecutingContext, next);