Browse Source

Update for hateos in UI

pull/363/head
Sebastian Stehle 7 years ago
parent
commit
84474dfb2f
  1. 24
      src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardHelper.cs
  2. 2
      src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs
  3. 48
      src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs
  4. 3
      src/Squidex.Shared/Permissions.cs
  5. 4
      src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs
  6. 53
      src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldDto.cs
  7. 37
      src/Squidex/Areas/Api/Controllers/Schemas/Models/NestedFieldDto.cs
  8. 20
      src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDetailsDto.cs
  9. 42
      src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaDto.cs
  10. 10
      src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs
  11. 2
      src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs
  12. 8
      src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs
  13. 4
      src/Squidex/app/features/administration/pages/users/user-page.component.ts
  14. 2
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.html
  15. 4
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts
  16. 6
      src/Squidex/app/features/schemas/pages/schema/field.component.html
  17. 16
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  18. 14
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
  19. 5
      src/Squidex/app/features/settings/pages/languages/language.component.html
  20. 30
      src/Squidex/app/framework/angular/sorted.directive.ts
  21. 2
      src/Squidex/app/framework/utils/hateos.ts
  22. 4
      src/Squidex/app/shared/components/asset-dialog.component.ts
  23. 4
      src/Squidex/app/shared/components/asset.component.ts
  24. 40
      src/Squidex/app/shared/components/schema-category.component.html
  25. 9
      src/Squidex/app/shared/components/schema-category.component.ts
  26. 4
      src/Squidex/app/shared/services/schemas.service.spec.ts
  27. 42
      src/Squidex/app/shared/services/schemas.service.ts
  28. 8
      tests/Squidex.Web.Tests/ApiPermissionAttributeTests.cs

24
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.");
}
}
}
}

2
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 =>

48
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)
{

3
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";

4
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<IActionResult> GetJob()
{
var restoreGrain = grainFactory.GetGrain<IRestoreGrain>(SingleGrain.Id);
@ -67,7 +67,7 @@ namespace Squidex.Areas.Api.Controllers.Backups
/// </returns>
[HttpPost]
[Route("apps/restore/")]
[ApiPermission(Permissions.AdminRestoreCreate)]
[ApiPermission(Permissions.AdminRestore)]
public async Task<IActionResult> PostRestore([FromBody] RestoreRequest request)
{
var restoreGrain = grainFactory.GetGrain<IRestoreGrain>(SingleGrain.Id);

53
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
{
/// <summary>
/// The id of the field.
@ -55,5 +57,54 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// The nested fields.
/// </summary>
public List<NestedFieldDto> 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<SchemaFieldsController>(x => nameof(x.PutField), values));
if (IsHidden)
{
AddPutLink("show", controller.Url<SchemaFieldsController>(x => nameof(x.ShowField), values));
}
else
{
AddPutLink("hide", controller.Url<SchemaFieldsController>(x => nameof(x.HideField), values));
}
if (IsDisabled)
{
AddPutLink("enable", controller.Url<SchemaFieldsController>(x => nameof(x.EnableField), values));
}
else
{
AddPutLink("show", controller.Url<SchemaFieldsController>(x => nameof(x.DisableField), values));
}
if (Properties is ArrayFieldPropertiesDto)
{
AddPostLink("fields/add", controller.Url<SchemaFieldsController>(x => nameof(x.PostNestedField), values));
AddPutLink("order", controller.Url<SchemaFieldsController>(x => nameof(x.PutNestedFieldOrdering), values));
}
AddPutLink("lock", controller.Url<SchemaFieldsController>(x => nameof(x.LockField), values));
AddDeleteLink("delete", controller.Url<SchemaFieldsController>(x => nameof(x.DeleteField), values));
}
if (Nested != null)
{
foreach (var nested in Nested)
{
nested.CreateLinks(controller, app, schema, FieldId, allowUpdate);
}
}
}
}
}

37
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
{
/// <summary>
/// The id of the field.
@ -43,5 +44,39 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models
/// </summary>
[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<SchemaFieldsController>(x => nameof(x.PutNestedField), values));
if (IsHidden)
{
AddPutLink("show", controller.Url<SchemaFieldsController>(x => nameof(x.ShowNestedField), values));
}
else
{
AddPutLink("hide", controller.Url<SchemaFieldsController>(x => nameof(x.HideNestedField), values));
}
if (IsDisabled)
{
AddPutLink("enable", controller.Url<SchemaFieldsController>(x => nameof(x.EnableNestedField), values));
}
else
{
AddPutLink("show", controller.Url<SchemaFieldsController>(x => nameof(x.DisableNestedField), values));
}
AddPutLink("lock", controller.Url<SchemaFieldsController>(x => nameof(x.LockNestedField), values));
AddDeleteLink("delete", controller.Url<SchemaFieldsController>(x => nameof(x.DeleteNestedField), values));
}
}
}
}

20
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);
}
}
}
}
}

42
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<SchemasController>(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<ContentsController>(x => nameof(x.GetContents), values));
}
return this;
if (controller.HasPermission(Permissions.AppSchemasPublish, app, Name))
{
if (IsPublished)
{
AddPutLink("unpublish", controller.Url<SchemasController>(x => nameof(x.UnpublishSchema), values));
}
else
{
AddPutLink("publish", controller.Url<SchemasController>(x => nameof(x.PublishSchema), values));
}
}
if (allowUpdate)
{
AddPutLink("order", controller.Url<SchemaFieldsController>(x => nameof(x.PutSchemaFieldOrdering), values));
AddPutLink("update", controller.Url<SchemasController>(x => nameof(x.PutSchema), values));
AddPutLink("update/category", controller.Url<SchemasController>(x => nameof(x.PutCategory), values));
AddPutLink("update/sync", controller.Url<SchemasController>(x => nameof(x.PutSchemaSync), values));
AddPutLink("update/urls", controller.Url<SchemasController>(x => nameof(x.PutPreviewUrls), values));
AddPostLink("fields/add", controller.Url<SchemaFieldsController>(x => nameof(x.PostField), values));
}
if (controller.HasPermission(Permissions.AppSchemasScripts, app, Name))
{
AddPutLink("update/scripts", controller.Url<SchemasController>(x => nameof(x.PutScripts), values));
}
if (controller.HasPermission(Permissions.AppSchemasDelete, app, Name))
{
AddDeleteLink("delete", controller.Url<SchemasController>(x => nameof(x.DeleteSchema), values));
}
}
}
}

10
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<SchemasController>(x => nameof(x.GetSchemas), values));
if (controller.HasPermission(Permissions.AppSchemasCreate, app))
{
AddPostLink("create", controller.Url<SchemasController>(x => nameof(x.PostSchema), values));
}
return this;
}
}

2
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<IActionResult> PutSchemaScripts(string app, string name, [FromBody] SchemaScriptsDto request)
public async Task<IActionResult> PutScripts(string app, string name, [FromBody] SchemaScriptsDto request)
{
var command = request.ToCommand();

8
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<PingController>(x => nameof(x.GetPing)));
result.AddGetLink("languages", controller.Url<LanguagesController>(x => nameof(x.GetLanguages)));
if (controller.HasPermission(Permissions.AdminEventsRead))
{
result.AddGetLink("admin/eventConsumers", controller.Url<EventConsumersController>(x => nameof(x.GetEventConsumers)));
result.AddGetLink("admin/events", controller.Url<EventConsumersController>(x => nameof(x.GetEventConsumers)));
}
if (controller.HasPermission(Permissions.AdminRestoreRead))
if (controller.HasPermission(Permissions.AdminRestore))
{
result.AddGetLink("admin/restore", controller.Url<RestoreController>(x => nameof(x.GetJob)));
}
@ -39,6 +37,8 @@ namespace Squidex.Areas.Api.Controllers.Users.Models
result.AddGetLink("admin/users", controller.Url<UserManagementController>(x => nameof(x.GetUsers)));
}
result.AddGetLink("languages", controller.Url<LanguagesController>(x => nameof(x.GetLanguages)));
return result;
}
}

4
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();

2
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">
</sqx-schema-category>
</ng-container>
</ng-container>

4
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;

6
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">
</sqx-field-form>
</form>
@ -90,7 +91,10 @@
<ng-container *ngIf="field['nested']; let nested">
<span class="nested-field-line-v"></span>
<div [sqxSortModel]="nested" (sqxSort)="sortFields($event)">
<div
[sqxSortDisabled]="!isEditable"
[sqxSortModel]="nested"
(sqxSort)="sortFields($event)">
<div class="nested-field" *ngFor="let nested of nested; trackBy: trackByField.bind(this)">
<span class="nested-field-line-h"></span>

16
src/Squidex/app/features/schemas/pages/schema/schema-page.component.html

@ -31,11 +31,13 @@
Edit Preview Urls
</a>
<div class="dropdown-divider"></div>
<ng-container *ngIf="schemasState.links | async | sqxHasLink:'create'">
<div class="dropdown-divider"></div>
<a class="dropdown-item" (click)="cloneSchema()">
Clone
</a>
<a class="dropdown-item" (click)="cloneSchema()">
Clone
</a>
</ng-container>
<ng-container *ngIf="schema | sqxHasLink:'delete'">
<div class="dropdown-divider"></div>
@ -63,14 +65,14 @@
<div class="table-items-row table-items-row-empty" *ngIf="schema && schema.fields.length === 0">
No field created yet.
<button type="button" class="btn btn-success btn-sm ml-2" (click)="addFieldDialog.show()" *ngIf="schemasState.links | async | sqxHasLink:'addField'">
<button type="button" class="btn btn-success btn-sm ml-2" (click)="addFieldDialog.show()" *ngIf="schemasState.links | async | sqxHasLink:'fields/add'">
<i class="icon icon-plus"></i> Add Field
</button>
</div>
<ng-container *ngIf="patternsState.patterns | async; let patterns">
<div class="schemas"
[disabled]="schema | sqxHasNoLink:'update'"
[sqxSortDisabled]="schema | sqxHasNoLink:'update'"
[sqxSortModel]="schema.fields"
(sqxSort)="sortFields($event)">
<div *ngFor="let field of schema.fields; trackBy: trackByField.bind(this)">
@ -78,7 +80,7 @@
</div>
</div>
<button type="button" class="btn btn-success field-button" (click)="addFieldDialog.show()" *ngIf="schemasState.links | async | sqxHasLink:'addField'">
<button type="button" class="btn btn-success field-button" (click)="addFieldDialog.show()" *ngIf="schema | sqxHasLink:'fields/add'">
<i class="icon icon-plus field-button-icon"></i> <div class="field-button-text">Add Field</div>
</button>
</ng-container>

14
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html

@ -6,12 +6,18 @@
</ng-container>
<ng-container secondHeader>
<sqx-shortcut keys="ctrl+shift+g" (trigger)="addSchemaDialog.show()"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut>
<button type="button" class="btn btn-success subheader-button" (click)="createSchema()" title="New Schema (CTRL + SHIFT + G)" titlePosition="top-left">
<i class="icon-plus"></i>
</button>
<ng-container>
<sqx-shortcut keys="ctrl+shift+g" (trigger)="addSchemaDialog.show()" [disabled]="schemasState.links | sqxHasNoLink:'create'"></sqx-shortcut>
<button type="button" class="btn btn-success subheader-button" (click)="createSchema()"
[disabled]="schemasState.links | async | sqxHasNoLink:'create'"
title="New Schema (CTRL + SHIFT + G)"
titlePosition="top-left">
<i class="icon-plus"></i>
</button>
</ng-container>
<div class="search-form">
<input class="form-control form-control-dark" #inputFind [formControl]="schemasFilter" placeholder="Search for schemas..." />

5
src/Squidex/app/features/settings/pages/languages/language.component.html

@ -39,7 +39,10 @@
<label class="col-3 col-form-label fallback-label">Fallback</label>
<div class="col-9">
<div class="fallback-languages" [sqxSortModel]="fallbackLanguages.mutableValues" [disabled]="!isEditable" *ngIf="fallbackLanguages.length > 0">
<div class="fallback-languages"
[sqxSortModel]="fallbackLanguages.mutableValues"
[sqxSortDisabled]="!isEditable"
*ngIf="fallbackLanguages.length > 0">
<div class="fallback-language" *ngFor="let language of fallbackLanguages">
<div class="row no-gutters">
<div class="col-auto" *ngIf="isEditable">

30
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<any[]>();
@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);
}
}

2
src/Squidex/app/framework/utils/hateos.ts

@ -48,7 +48,7 @@ export function withLinks<T extends Resource>(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);

4
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();

4
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<State> 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])

40
src/Squidex/app/shared/components/schema-category.component.html

@ -15,29 +15,29 @@
<ul class="nav nav-panel nav-dark nav-dark-bordered flex-column" *ngIf="snapshot.isOpen" @fade>
<ng-container *ngFor="let schema of snapshot.schemasFiltered; trackBy: trackBySchema">
<ng-container *ngIf="schemaPermission(schema)">
<li class="nav-item" dnd-draggable [dragEnabled]="!isReadonly" [dragData]="schema">
<a class="nav-link" [routerLink]="schemaRoute(schema)" routerLinkActive="active">
<div class="row" *ngIf="!isReadonly">
<div class="col-4">
<span class="schema-name schema-name-accent">{{schema.displayName}}</span>
</div>
<div class="col-4">
<span class="schema-user">
<i class="icon-user"></i> {{schema.lastModifiedBy | sqxUserNameRef}}
</span>
</div>
<div class="col-4 schema-modified">
<small class="item-modified">{{schema.lastModified | sqxFromNow}}</small>
<li class="nav-item" dnd-draggable [dragEnabled]="!forContent && schema | sqxHasLink:'update/category'" [dragData]="schema">
<a class="nav-link" [routerLink]="schemaRoute(schema)" routerLinkActive="active">
<div class="row" *ngIf="!forContent; else simpleMode">
<div class="col-4">
<span class="schema-name schema-name-accent">{{schema.displayName}}</span>
</div>
<div class="col-4">
<span class="schema-user">
<i class="icon-user"></i> {{schema.lastModifiedBy | sqxUserNameRef}}
</span>
</div>
<div class="col-4 schema-modified">
<small class="item-modified">{{schema.lastModified | sqxFromNow}}</small>
<span class="item-published" [class.unpublished]="!schema.isPublished"></span>
</div>
<span class="item-published" [class.unpublished]="!schema.isPublished"></span>
</div>
</div>
<span class="schema-name" *ngIf="isReadonly">{{schema.displayName}}</span>
</a>
</li>
</ng-container>
<ng-template #simpleMode>
<span class="schema-name" *ngIf="forContent">{{schema.displayName}}</span>
</ng-template>
</a>
</li>
</ng-container>
</ul>
</div>

9
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<State> implements
public name: string;
@Input()
public isReadonly: boolean;
public forContent: boolean;
@Input()
public routeSingletonToContent = false;
@ -122,7 +123,7 @@ export class SchemaCategoryComponent extends StatefulComponent<State> 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<State> 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`;
}

4
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' }
}
};

42
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<SchemaDetailsDto> {
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<SchemaDetailsDto> {
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);

8
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);

Loading…
Cancel
Save