Browse Source

More Hateos links.

pull/363/head
Sebastian Stehle 7 years ago
parent
commit
6d51a17402
  1. 8
      src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs
  2. 5
      src/Squidex.Web/Resource.cs
  3. 14
      src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs
  4. 16
      src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs
  5. 6
      src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
  6. 23
      src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs
  7. 22
      src/Squidex/Areas/Api/Controllers/Apps/Models/PatternsDto.cs
  8. 33
      src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs
  9. 12
      src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs
  10. 3
      src/Squidex/app/features/settings/pages/patterns/pattern.component.html
  11. 15
      src/Squidex/app/features/settings/pages/patterns/pattern.component.ts
  12. 2
      src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html
  13. 11
      src/Squidex/app/features/settings/pages/roles/role.component.html
  14. 14
      src/Squidex/app/features/settings/pages/roles/role.component.ts
  15. 2
      src/Squidex/app/features/settings/pages/roles/roles-page.component.html
  16. 2
      src/Squidex/app/shared/services/patterns.service.spec.ts
  17. 2
      src/Squidex/app/shared/services/patterns.service.ts
  18. 3
      src/Squidex/app/shared/services/roles.service.spec.ts
  19. 6
      src/Squidex/app/shared/services/roles.service.ts
  20. 4
      src/Squidex/app/shared/state/patterns.state.ts
  21. 4
      src/Squidex/app/shared/state/roles.state.ts

8
src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs

@ -7,6 +7,7 @@
using Squidex.Infrastructure;
using Squidex.Infrastructure.Security;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using P = Squidex.Shared.Permissions;
@ -20,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Apps
public const string Owner = "Owner";
public const string Reader = "Reader";
private static readonly HashSet<string> DefaultRolesSet = new HashSet<string>
private static readonly HashSet<string> DefaultRolesSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
Editor,
Developer,
@ -54,6 +55,11 @@ namespace Squidex.Domain.Apps.Core.Apps
return role != null && DefaultRolesSet.Contains(role);
}
public static bool IsRole(string name, string expected)
{
return name != null && string.Equals(name, expected, StringComparison.OrdinalIgnoreCase);
}
public static Role CreateOwner(string app)
{
return new Role(Owner,

5
src/Squidex.Web/Resource.cs

@ -6,6 +6,7 @@
// ==========================================================================
using Newtonsoft.Json;
using Squidex.Infrastructure;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@ -50,6 +51,10 @@ namespace Squidex.Web
public void AddLink(string rel, string method, string href)
{
Guard.NotNullOrEmpty(rel, nameof(rel));
Guard.NotNullOrEmpty(href, nameof(href));
Guard.NotNullOrEmpty(method, nameof(method));
Links[rel] = new ResourceLink { Href = href, Method = method };
}
}

14
src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs

@ -42,12 +42,12 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </remarks>
[HttpGet]
[Route("apps/{app}/patterns/")]
[ProducesResponseType(typeof(AppPatternsDto), 200)]
[ProducesResponseType(typeof(PatternsDto), 200)]
[ApiPermission(Permissions.AppCommon)]
[ApiCosts(0)]
public IActionResult GetPatterns(string app)
{
var response = AppPatternsDto.FromApp(App, this);
var response = PatternsDto.FromApp(App, this);
Response.Headers[HeaderNames.ETag] = App.Version.ToString();
@ -66,7 +66,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </returns>
[HttpPost]
[Route("apps/{app}/patterns/")]
[ProducesResponseType(typeof(AppPatternsDto), 200)]
[ProducesResponseType(typeof(PatternsDto), 200)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppPatternsCreate)]
[ApiCosts(1)]
@ -92,7 +92,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </returns>
[HttpPut]
[Route("apps/{app}/patterns/{id}/")]
[ProducesResponseType(typeof(AppPatternsDto), 200)]
[ProducesResponseType(typeof(PatternsDto), 200)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppPatternsUpdate)]
[ApiCosts(1)]
@ -119,7 +119,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </remarks>
[HttpDelete]
[Route("apps/{app}/patterns/{id}/")]
[ProducesResponseType(typeof(AppPatternsDto), 200)]
[ProducesResponseType(typeof(PatternsDto), 200)]
[ApiPermission(Permissions.AppPatternsDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeletePattern(string app, Guid id)
@ -131,12 +131,12 @@ namespace Squidex.Areas.Api.Controllers.Apps
return Ok(response);
}
private async Task<AppPatternsDto> InvokeCommandAsync(ICommand command)
private async Task<PatternsDto> InvokeCommandAsync(ICommand command)
{
var context = await CommandBus.PublishAsync(command);
var result = context.Result<IAppEntity>();
var response = AppPatternsDto.FromApp(result, this);
var response = PatternsDto.FromApp(result, this);
return response;
}

16
src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs

@ -105,7 +105,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// Update an existing app role.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="role">The name of the role to be updated.</param>
/// <param name="name">The name of the role to be updated.</param>
/// <param name="request">Role to be updated for the app.</param>
/// <returns>
/// 200 => Role updated.
@ -113,13 +113,13 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// 404 => Role or app not found.
/// </returns>
[HttpPut]
[Route("apps/{app}/roles/{role}/")]
[Route("apps/{app}/roles/{name}/")]
[ProducesResponseType(typeof(RolesDto), 200)]
[ApiPermission(Permissions.AppRolesUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> UpdateRole(string app, string role, [FromBody] UpdateRoleDto request)
public async Task<IActionResult> UpdateRole(string app, string name, [FromBody] UpdateRoleDto request)
{
var command = request.ToCommand(role);
var command = request.ToCommand(name);
var response = await InvokeCommandAsync(command);
@ -130,21 +130,21 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// Remove role from app.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="role">The name of the role.</param>
/// <param name="name">The name of the role.</param>
/// <returns>
/// 200 => Role deleted.
/// 400 => Role is in use by contributor or client or default role.
/// 404 => Role or app not found.
/// </returns>
[HttpDelete]
[Route("apps/{app}/roles/{role}/")]
[Route("apps/{app}/roles/{name}/")]
[ProducesResponseType(typeof(RolesDto), 200)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppRolesDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeleteRole(string app, string role)
public async Task<IActionResult> DeleteRole(string app, string name)
{
var command = new DeleteRole { Name = role };
var command = new DeleteRole { Name = name };
var response = await InvokeCommandAsync(command);

6
src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs

@ -8,7 +8,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using NodaTime;
using Squidex.Areas.Api.Controllers.Assets;
using Squidex.Areas.Api.Controllers.Backups;
@ -18,7 +17,6 @@ using Squidex.Areas.Api.Controllers.Rules;
using Squidex.Areas.Api.Controllers.Schemas;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security;
using Squidex.Shared;
@ -59,7 +57,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// <summary>
/// The permission level of the user.
/// </summary>
public string[] Permissions { get; set; }
public IEnumerable<string> Permissions { get; set; }
/// <summary>
/// Indicates if the user can access the api.
@ -87,7 +85,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
var result = SimpleMapper.Map(app, new AppDto());
result.Permissions = permissions.ToIds().ToArray();
result.Permissions = permissions.ToIds();
result.PlanName = plans.GetPlanForApp(app)?.Name;
result.CanAccessApi = controller.HasPermission(AllPermissions.AppApi, app.Name, "*", permissions);

23
src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs → src/Squidex/Areas/Api/Controllers/Apps/Models/PatternDto.cs

@ -9,16 +9,17 @@ using System;
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Infrastructure.Reflection;
using Squidex.Shared;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
public sealed class AppPatternDto : Resource
public sealed class PatternDto : Resource
{
/// <summary>
/// Unique id of the pattern.
/// </summary>
public Guid PatternId { get; set; }
public Guid Id { get; set; }
/// <summary>
/// The name of the suggestion.
@ -37,15 +38,27 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// </summary>
public string Message { get; set; }
public static AppPatternDto FromPattern(Guid id, AppPattern pattern, ApiController controller, string app)
public static PatternDto FromPattern(Guid id, AppPattern pattern, ApiController controller, string app)
{
var result = SimpleMapper.Map(pattern, new AppPatternDto { PatternId = id });
var result = SimpleMapper.Map(pattern, new PatternDto { Id = id });
return result.CreateLinks(controller, app);
}
private AppPatternDto CreateLinks(ApiController controller, string app)
private PatternDto CreateLinks(ApiController controller, string app)
{
var values = new { app, id = Id };
if (controller.HasPermission(Permissions.AppPatternsUpdate, app))
{
AddPutLink("update", controller.Url<AppPatternsController>(x => nameof(x.UpdatePattern), values));
}
if (controller.HasPermission(Permissions.AppPatternsDelete, app))
{
AddDeleteLink("delete", controller.Url<AppPatternsController>(x => nameof(x.DeletePattern), values));
}
return this;
}
}

22
src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternsDto.cs → src/Squidex/Areas/Api/Controllers/Apps/Models/PatternsDto.cs

@ -8,30 +8,40 @@
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Shared;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
public sealed class AppPatternsDto : Resource
public sealed class PatternsDto : Resource
{
/// <summary>
/// The patterns.
/// </summary>
[Required]
public AppPatternDto[] Items { get; set; }
public PatternDto[] Items { get; set; }
public static AppPatternsDto FromApp(IAppEntity app, ApiController controller)
public static PatternsDto FromApp(IAppEntity app, ApiController controller)
{
var result = new AppPatternsDto
var result = new PatternsDto
{
Items = app.Patterns.Select(x => AppPatternDto.FromPattern(x.Key, x.Value, controller, app.Name)).ToArray()
Items = app.Patterns.Select(x => PatternDto.FromPattern(x.Key, x.Value, controller, app.Name)).ToArray()
};
return result.CreateLinks(controller, app.Name);
}
private AppPatternsDto CreateLinks(ApiController controller, string app)
private PatternsDto CreateLinks(ApiController controller, string app)
{
var values = new { app };
AddSelfLink(controller.Url<AppPatternsController>(x => nameof(x.GetPatterns), values));
if (controller.HasPermission(Permissions.AppPatternsCreate, app))
{
AddPostLink("create", controller.Url<AppPatternsController>(x => nameof(x.PostPattern), values));
}
return this;
}
}

33
src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs

@ -5,17 +5,17 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Web;
using AllPermissions = Squidex.Shared.Permissions;
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
public sealed class RoleDto
public sealed class RoleDto : Resource
{
/// <summary>
/// The role name.
@ -33,6 +33,11 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
/// </summary>
public int NumContributors { get; set; }
/// <summary>
/// Indicates if the role is an builtin default role.
/// </summary>
public bool IsDefaultRole { get; set; }
/// <summary>
/// Associated list of permissions.
/// </summary>
@ -46,16 +51,32 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
var result = new RoleDto
{
Name = role.Name,
NumClients = app.Clients.Count(x => string.Equals(x.Value.Role, role.Name, StringComparison.OrdinalIgnoreCase)),
NumContributors = app.Contributors.Count(x => string.Equals(x.Value, role.Name, StringComparison.OrdinalIgnoreCase)),
Permissions = permissions.ToIds()
NumClients = app.Clients.Count(x => Role.IsRole(x.Value.Role, role.Name)),
NumContributors = app.Contributors.Count(x => Role.IsRole(x.Value, role.Name)),
Permissions = permissions.ToIds(),
IsDefaultRole = Role.IsDefaultRole(role.Name)
};
return result.CreateLinks(controller, app.Name);
}
private RoleDto CreateLinks(ApiController controller, string name)
private RoleDto CreateLinks(ApiController controller, string app)
{
var values = new { app, name = Name };
if (!IsDefaultRole)
{
if (controller.HasPermission(AllPermissions.AppRolesUpdate, app) && NumClients == 0 && NumContributors == 0)
{
AddPutLink("update", controller.Url<AppRolesController>(x => nameof(x.UpdateRole), values));
}
if (controller.HasPermission(AllPermissions.AppRolesDelete, app))
{
AddDeleteLink("delete", controller.Url<AppRolesController>(x => nameof(x.DeleteRole), values));
}
}
return this;
}
}

12
src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs

@ -8,6 +8,7 @@
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Shared;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models
@ -24,7 +25,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
{
var result = new RolesDto
{
Items = app.Roles.Values.Select(x => RoleDto.FromRole(x, app, controller)).ToArray()
Items = app.Roles.Values.Select(x => RoleDto.FromRole(x, app, controller)).OrderBy(x => x.Name).ToArray()
};
return result.CreateLinks(controller, app.Name);
@ -32,6 +33,15 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
private RolesDto CreateLinks(ApiController controller, string app)
{
var values = new { app };
AddSelfLink(controller.Url<AppRolesController>(x => nameof(x.GetRoles), values));
if (controller.HasPermission(Permissions.AppRolesCreate, app))
{
AddPostLink("create", controller.Url<AppRolesController>(x => nameof(x.PostRole), values));
}
return this;
}
}

3
src/Squidex/app/features/settings/pages/patterns/pattern.component.html

@ -20,11 +20,12 @@
</div>
<div class="col-auto pl-1 col-options" *ngIf="pattern">
<button type="submit" class="btn btn-primary mr-1" [class.disabled]="!editForm.form.dirty">
<button type="submit" class="btn btn-primary mr-1" [disabled]="!editForm.form.dirty || !isEditable">
<i class="icon-checkmark"></i>
</button>
<button type="button" class="btn btn-text-danger"
[disabled]="!isDeletable"
(sqxConfirmClick)="delete()"
confirmTitle="Remove pattern"
confirmText="Do you really want to remove this pattern?">

15
src/Squidex/app/features/settings/pages/patterns/pattern.component.ts

@ -10,6 +10,7 @@ import { FormBuilder } from '@angular/forms';
import {
EditPatternForm,
hasAnyLink,
PatternDto,
PatternsState
} from '@app/shared';
@ -25,6 +26,9 @@ export class PatternComponent implements OnInit {
public editForm = new EditPatternForm(this.formBuilder);
public isEditable = false;
public isDeletable = false;
constructor(
private readonly patternsState: PatternsState,
private readonly formBuilder: FormBuilder
@ -32,7 +36,14 @@ export class PatternComponent implements OnInit {
}
public ngOnInit() {
this.isEditable = !this.pattern || hasAnyLink(this.pattern, 'update');
this.isDeletable = !this.pattern && hasAnyLink(this.pattern, 'delete');
this.editForm.load(this.pattern);
if (!this.isEditable) {
this.editForm.form.disable();
}
}
public cancel() {
@ -44,6 +55,10 @@ export class PatternComponent implements OnInit {
}
public save() {
if (!this.isEditable) {
return;
}
const value = this.editForm.submit();
if (value) {

2
src/Squidex/app/features/settings/pages/patterns/patterns-page.component.html

@ -23,7 +23,7 @@
[pattern]="pattern">
</sqx-pattern>
<sqx-pattern></sqx-pattern>
<sqx-pattern *ngIf="patternsState.links | async | sqxHasLink:'create'"></sqx-pattern>
</ng-container>
</ng-container>

11
src/Squidex/app/features/settings/pages/roles/role.component.html

@ -1,7 +1,7 @@
<div class="table-items-row table-items-row-expandable">
<div class="table-items-row-summary">
<div class="row">
<div class="col-5" [class.built]="isDefaultRole">
<div class="col-5" [class.built]="role.isDefaultRole">
<span class="role-name">{{role.name}}</span>
</div>
<div class="col text-decent">
@ -16,7 +16,8 @@
<i class="icon-settings"></i>
</button>
<button type="button" class="btn btn-text-danger" [class.invisible]="isDefaultRole || role.numClients > 0 || role.numContributors > 0"
<button type="button" class="btn btn-text-danger"
[disabled]="role | sqxHasNoLink:'delete'"
(sqxConfirmClick)="remove()"
confirmTitle="Delete role"
confirmText="Do you really want to delete the role?">
@ -32,7 +33,7 @@
<div class="table-items-row-details-tabs clearfix">
<div class="float-right">
<button type="reset" class="btn btn-text" (click)="toggleEditing()">Cancel</button>
<button type="submit" class="btn btn-primary" *ngIf="!isDefaultRole">Save</button>
<button type="submit" class="btn btn-primary" *ngIf="isEditable">Save</button>
</div>
</div>
@ -43,14 +44,14 @@
<sqx-autocomplete [formControl]="control" [source]="allPermissions"></sqx-autocomplete>
</div>
<div class="col-auto pl-1" *ngIf="!isDefaultRole">
<div class="col-auto pl-1" *ngIf="isEditable">
<button type="button" class="btn btn-text-danger" (click)="removePermission(i)">
<i class="icon-bin2"></i>
</button>
</div>
</div>
<form class="form-group row no-gutters" [formGroup]="addPermissionForm.form" (ngSubmit)="addPermission()" *ngIf="!isDefaultRole">
<form class="form-group row no-gutters" [formGroup]="addPermissionForm.form" (ngSubmit)="addPermission()" *ngIf="isEditable">
<div class="col">
<sqx-autocomplete formControlName="permission" [source]="allPermissions" #addInput></sqx-autocomplete>
</div>

14
src/Squidex/app/features/settings/pages/roles/role.component.ts

@ -14,17 +14,11 @@ import {
AutocompleteSource,
EditPermissionsForm,
fadeAnimation,
hasAnyLink,
RoleDto,
RolesState
} from '@app/shared';
const DEFAULT_ROLES = [
'Owner',
'Developer',
'Editor',
'Reader'
];
@Component({
selector: 'sqx-role',
styleUrls: ['./role.component.scss'],
@ -44,7 +38,7 @@ export class RoleComponent implements OnChanges {
public addPermissionInput: AutocompleteComponent;
public isEditing = false;
public isDefaultRole = false;
public isEditable = false;
public addPermissionForm = new AddPermissionForm(this.formBuilder);
@ -57,11 +51,11 @@ export class RoleComponent implements OnChanges {
}
public ngOnChanges() {
this.isDefaultRole = DEFAULT_ROLES.indexOf(this.role.name) >= 0;
this.isEditable = hasAnyLink(this.role, 'update');
this.editForm.load(this.role.permissions);
if (this.isDefaultRole) {
if (!this.isEditable) {
this.editForm.form.disable();
}
}

2
src/Squidex/app/features/settings/pages/roles/roles-page.component.html

@ -17,7 +17,7 @@
<ng-container *ngIf="(rolesState.isLoaded | async) && (rolesState.roles | async); let roles">
<sqx-role *ngFor="let role of roles" [role]="role" [allPermissions]="allPermissions"></sqx-role>
<div class="table-items-footer">
<div class="table-items-footer" *ngIf="rolesState.links | async | sqxHasLink:'create'">
<form [formGroup]="addRoleForm.form" (ngSubmit)="addRole()">
<div class="row no-gutters">
<div class="col">

2
src/Squidex/app/shared/services/patterns.service.spec.ts

@ -151,8 +151,8 @@ describe('PatternsService', () => {
function patternsResponse(...ids: number[]) {
return {
items: ids.map(id => ({
id: `id${id}`,
name: `Name${id}`,
patternId: `id${id}`,
pattern: `Pattern${id}`,
message: `Message${id}`,
_links: {

2
src/Squidex/app/shared/services/patterns.service.ts

@ -115,7 +115,7 @@ function parsePatterns(response: any) {
const patterns = items.map(item =>
withLinks(
new PatternDto(
item.patternId,
item.id,
item.name,
item.pattern,
item.message),

3
src/Squidex/app/shared/services/roles.service.spec.ts

@ -174,6 +174,7 @@ describe('RolesService', () => {
numClients: id * 2,
numContributors: id * 3,
permissions: [`permission${id}`],
isDefaultRole: id % 2 === 0,
_links: {
update: { method: 'PUT', href: `/roles/id${id}` }
}
@ -189,7 +190,7 @@ export function createRoles(...ids: number[]): RolesPayload {
return {
items: ids.map(id =>
withLinks(
new RoleDto(`name${id}`, id * 2, id * 3, [`permission${id}`]),
new RoleDto(`name${id}`, id * 2, id * 3, [`permission${id}`], id % 2 === 0),
{
_links: {
update: { method: 'PUT', href: `/roles/id${id}` }

6
src/Squidex/app/shared/services/roles.service.ts

@ -35,7 +35,8 @@ export class RoleDto {
public readonly name: string,
public readonly numClients: number,
public readonly numContributors: number,
public readonly permissions: string[]
public readonly permissions: string[],
public readonly isDefaultRole: boolean
) {
}
}
@ -127,7 +128,8 @@ export function parseRoles(response: any) {
item.name,
item.numClients,
item.numContributors,
item.permissions),
item.permissions,
item.isDefaultRole),
item));
return withLinks({ items: roles, _links: {} }, response);

4
src/Squidex/app/shared/state/patterns.state.ts

@ -54,6 +54,10 @@ export class PatternsState extends State<Snapshot> {
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
public links =
this.changes.pipe(map(x => x.links),
distinctUntilChanged());
constructor(
private readonly patternsService: PatternsService,
private readonly appsState: AppsState,

4
src/Squidex/app/shared/state/roles.state.ts

@ -54,6 +54,10 @@ export class RolesState extends State<Snapshot> {
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
public links =
this.changes.pipe(map(x => x.links),
distinctUntilChanged());
constructor(
private readonly rolesService: RolesService,
private readonly appsState: AppsState,

Loading…
Cancel
Save