diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs index fef02c374..b2d924b77 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -6,11 +6,11 @@ // ========================================================================== using System; -using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using Squidex.Areas.Api.Controllers.Apps.Models; +using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Commands; using Squidex.Shared; @@ -42,12 +42,12 @@ namespace Squidex.Areas.Api.Controllers.Apps /// [HttpGet] [Route("apps/{app}/patterns/")] - [ProducesResponseType(typeof(AppPatternDto[]), 200)] + [ProducesResponseType(typeof(AppPatternsDto), 200)] [ApiPermission(Permissions.AppCommon)] [ApiCosts(0)] public IActionResult GetPatterns(string app) { - var response = App.Patterns.Select(AppPatternDto.FromKvp).OrderBy(x => x.Name).ToArray(); + var response = AppPatternsDto.FromApp(App, this); Response.Headers[HeaderNames.ETag] = App.Version.ToString(); @@ -66,16 +66,15 @@ namespace Squidex.Areas.Api.Controllers.Apps /// [HttpPost] [Route("apps/{app}/patterns/")] - [ProducesResponseType(typeof(AppPatternDto), 201)] + [ProducesResponseType(typeof(AppPatternsDto), 200)] + [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppPatternsCreate)] [ApiCosts(1)] public async Task PostPattern(string app, [FromBody] UpdatePatternDto request) { var command = request.ToAddCommand(); - await CommandBus.PublishAsync(command); - - var response = AppPatternDto.FromCommand(command); + var response = await InvokeCommandAsync(command); return CreatedAtAction(nameof(GetPatterns), new { app }, response); } @@ -93,14 +92,17 @@ namespace Squidex.Areas.Api.Controllers.Apps /// [HttpPut] [Route("apps/{app}/patterns/{id}/")] - [ProducesResponseType(typeof(AppPatternDto), 201)] + [ProducesResponseType(typeof(AppPatternsDto), 200)] + [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppPatternsUpdate)] [ApiCosts(1)] public async Task UpdatePattern(string app, Guid id, [FromBody] UpdatePatternDto request) { - await CommandBus.PublishAsync(request.ToUpdateCommand(id)); + var command = request.ToUpdateCommand(id); + + var response = await InvokeCommandAsync(command); - return NoContent(); + return Ok(response); } /// @@ -109,7 +111,7 @@ namespace Squidex.Areas.Api.Controllers.Apps /// The name of the app. /// The id of the pattern to be deleted. /// - /// 204 => Pattern removed. + /// 200 => Pattern removed. /// 404 => Pattern or app not found. /// /// @@ -117,13 +119,26 @@ namespace Squidex.Areas.Api.Controllers.Apps /// [HttpDelete] [Route("apps/{app}/patterns/{id}/")] + [ProducesResponseType(typeof(AppPatternsDto), 200)] [ApiPermission(Permissions.AppPatternsDelete)] [ApiCosts(1)] public async Task DeletePattern(string app, Guid id) { - await CommandBus.PublishAsync(new DeletePattern { PatternId = id }); + var command = new DeletePattern { PatternId = id }; + + var response = await InvokeCommandAsync(command); + + return Ok(response); + } + + private async Task InvokeCommandAsync(ICommand command) + { + var context = await CommandBus.PublishAsync(command); + + var result = context.Result(); + var response = AppPatternsDto.FromApp(result, this); - return NoContent(); + return response; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs index ad8efa50f..335c29a8d 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternDto.cs @@ -6,15 +6,14 @@ // ========================================================================== using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Squidex.Domain.Apps.Core.Apps; -using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure.Reflection; +using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models { - public sealed class AppPatternDto + public sealed class AppPatternDto : Resource { /// /// Unique id of the pattern. @@ -38,14 +37,16 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// public string Message { get; set; } - public static AppPatternDto FromKvp(KeyValuePair kvp) + public static AppPatternDto FromPattern(Guid id, AppPattern pattern, ApiController controller, string app) { - return SimpleMapper.Map(kvp.Value, new AppPatternDto { PatternId = kvp.Key }); + var result = SimpleMapper.Map(pattern, new AppPatternDto { PatternId = id }); + + return result.CreateLinks(controller, app); } - public static AppPatternDto FromCommand(AddPattern command) + private AppPatternDto CreateLinks(ApiController controller, string app) { - return SimpleMapper.Map(command, new AppPatternDto()); + return this; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternsDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternsDto.cs new file mode 100644 index 000000000..b0e03a577 --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppPatternsDto.cs @@ -0,0 +1,38 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Web; + +namespace Squidex.Areas.Api.Controllers.Apps.Models +{ + public sealed class AppPatternsDto : Resource + { + /// + /// The patterns. + /// + [Required] + public AppPatternDto[] Items { get; set; } + + public static AppPatternsDto FromApp(IAppEntity app, ApiController controller) + { + var result = new AppPatternsDto + { + Items = app.Patterns.Select(x => AppPatternDto.FromPattern(x.Key, x.Value, controller, app.Name)).ToArray() + }; + + return result.CreateLinks(controller, app.Name); + } + + private AppPatternsDto CreateLinks(ApiController controller, string app) + { + return this; + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs index 5f7c330fe..28b91c6ad 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs @@ -21,7 +21,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// The contributors. /// [Required] - public ContributorDto[] Contributors { get; set; } + public ContributorDto[] Items { get; set; } /// /// The maximum number of allowed contributors. @@ -40,7 +40,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models var result = new ContributorsDto { - Contributors = contributors, + Items = contributors, }; if (isInvited) diff --git a/src/Squidex/app/framework/utils/version.ts b/src/Squidex/app/framework/utils/version.ts index 1f4888e28..e01ff9bb1 100644 --- a/src/Squidex/app/framework/utils/version.ts +++ b/src/Squidex/app/framework/utils/version.ts @@ -6,6 +6,8 @@ */ export class Version { + public static readonly EMPTY = new Version(''); + constructor( public readonly value: string ) { diff --git a/src/Squidex/app/shared/services/clients.service.spec.ts b/src/Squidex/app/shared/services/clients.service.spec.ts index 17146a941..cfa04bf3f 100644 --- a/src/Squidex/app/shared/services/clients.service.spec.ts +++ b/src/Squidex/app/shared/services/clients.service.spec.ts @@ -172,7 +172,7 @@ describe('ClientsService', () => { function clientsResponse(...ids: number[]) { return { - contributors: ids.map(id => ({ + items: ids.map(id => ({ id: `id${id}`, name: `Client ${id}`, role: `Role${id}`, @@ -181,12 +181,8 @@ describe('ClientsService', () => { update: { method: 'PUT', href: `/clients/id${id}` } } })), - maxContributors: ids.length * 13, _links: { - create: { method: 'POST', href: '/contributors' } - }, - _meta: { - isInvited: 'true' + create: { method: 'POST', href: '/clients' } } }; } @@ -205,9 +201,6 @@ export function createClients(...ids: number[]): ClientsPayload { )), _links: { create: { method: 'POST', href: '/clients' } - }, - _meta: { - isInvited: 'true' } }; } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/clients.service.ts b/src/Squidex/app/shared/services/clients.service.ts index 050fa8a18..81c3c89be 100644 --- a/src/Squidex/app/shared/services/clients.service.ts +++ b/src/Squidex/app/shared/services/clients.service.ts @@ -139,7 +139,7 @@ export class ClientsService { } function parseClients(response: any): ClientsPayload { - const items: any[] = response; + const items: any[] = response.items; const clients = items.map(item => withLinks( diff --git a/src/Squidex/app/shared/services/contributors.service.spec.ts b/src/Squidex/app/shared/services/contributors.service.spec.ts index 4f0473a38..5363739cd 100644 --- a/src/Squidex/app/shared/services/contributors.service.spec.ts +++ b/src/Squidex/app/shared/services/contributors.service.spec.ts @@ -119,7 +119,7 @@ describe('ContributorsService', () => { function contributorsResponse(...ids: number[]) { return { - contributors: ids.map(id => ({ + items: ids.map(id => ({ contributorId: `id${id}`, role: id % 2 === 0 ? 'Owner' : 'Developer', _links: { update: { method: 'PUT', href: `/contributors/id${id}` } diff --git a/src/Squidex/app/shared/services/contributors.service.ts b/src/Squidex/app/shared/services/contributors.service.ts index 5fb0db81c..6bb1a98d4 100644 --- a/src/Squidex/app/shared/services/contributors.service.ts +++ b/src/Squidex/app/shared/services/contributors.service.ts @@ -96,7 +96,7 @@ export class ContributorsService { } function parseContributors(response: any) { - const items: any[] = response.contributors; + const items: any[] = response.items; const contributors = items.map(item => withLinks( diff --git a/src/Squidex/app/shared/services/patterns.service.spec.ts b/src/Squidex/app/shared/services/patterns.service.spec.ts index 8454db9da..2753fc6bb 100644 --- a/src/Squidex/app/shared/services/patterns.service.spec.ts +++ b/src/Squidex/app/shared/services/patterns.service.spec.ts @@ -13,8 +13,11 @@ import { ApiUrlConfig, PatternDto, PatternsDto, + PatternsPayload, PatternsService, - Version + Resource, + Version, + withLinks } from '@app/shared/internal'; describe('PatternsService', () => { @@ -51,31 +54,13 @@ describe('PatternsService', () => { expect(req.request.method).toEqual('GET'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush([ - { - patternId: '1', - pattern: '[0-9]', - name: 'Number', - message: 'Message1' - }, { - patternId: '2', - pattern: '[0-9]*', - name: 'Numbers', - message: 'Message2' - } - ], { + req.flush(patternsResponse(1, 2, 3), { headers: { etag: '2' } }); - expect(patterns!).toEqual({ - payload: [ - new PatternDto('1', 'Number', '[0-9]', 'Message1'), - new PatternDto('2', 'Numbers', '[0-9]*', 'Message2') - ], - version: new Version('2') - }); + expect(patterns!).toEqual({payload: createPatterns(1, 2, 3), version: new Version('2') }); })); it('should make post request to add pattern', @@ -83,10 +68,10 @@ describe('PatternsService', () => { const dto = { name: 'Number', pattern: '[0-9]' }; - let pattern: PatternDto; + let patterns: PatternsDto; patternService.postPattern('my-app', dto, version).subscribe(result => { - pattern = result.payload; + patterns = result; }); const req = httpMock.expectOne('http://service/p/api/apps/my-app/patterns'); @@ -94,14 +79,13 @@ describe('PatternsService', () => { expect(req.request.method).toEqual('POST'); expect(req.request.headers.get('If-Match')).toEqual(version.value); - req.flush({ - name: 'Number', - patternId: '1', - pattern: '[0-9]', - message: 'Message1' + req.flush(patternsResponse(1, 2, 3), { + headers: { + etag: '2' + } }); - expect(pattern!).toEqual(new PatternDto('1', 'Number', '[0-9]', 'Message1')); + expect(patterns!).toEqual({payload: createPatterns(1, 2, 3), version: new Version('2') }); })); it('should make put request to update pattern', @@ -109,26 +93,92 @@ describe('PatternsService', () => { const dto = { name: 'Number', pattern: '[0-9]' }; - patternService.putPattern('my-app', '1', dto, version).subscribe(); + const resource: Resource = { + _links: { + update: { method: 'PUT', href: '/api/apps/my-app/patterns/1' } + } + }; + + let patterns: PatternsDto; + + patternService.putPattern('my-app', resource, dto, version).subscribe(result => { + patterns = result; + }); const req = httpMock.expectOne('http://service/p/api/apps/my-app/patterns/1'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toEqual(version.value); - req.flush({}); + req.flush(patternsResponse(1, 2, 3), { + headers: { + etag: '2' + } + }); + + expect(patterns!).toEqual({payload: createPatterns(1, 2, 3), version: new Version('2') }); })); it('should make delete request to remove pattern', inject([PatternsService, HttpTestingController], (patternService: PatternsService, httpMock: HttpTestingController) => { - patternService.deletePattern('my-app', '1', version).subscribe(); + const resource: Resource = { + _links: { + delete: { method: 'DELETE', href: '/api/apps/my-app/patterns/1' } + } + }; + + let patterns: PatternsDto; + + patternService.deletePattern('my-app', resource, version).subscribe(result => { + patterns = result; + }); const req = httpMock.expectOne('http://service/p/api/apps/my-app/patterns/1'); expect(req.request.method).toEqual('DELETE'); expect(req.request.headers.get('If-Match')).toEqual(version.value); - req.flush({}); + req.flush(patternsResponse(1, 2, 3), { + headers: { + etag: '2' + } + }); + + expect(patterns!).toEqual({payload: createPatterns(1, 2, 3), version: new Version('2') }); })); -}); \ No newline at end of file + + function patternsResponse(...ids: number[]) { + return { + items: ids.map(id => ({ + name: `Name${id}`, + patternId: `id${id}`, + pattern: `Pattern${id}`, + message: `Message${id}`, + _links: { + update: { method: 'PUT', href: `/patterns/id${id}` } + } + })), + _links: { + create: { method: 'POST', href: '/patterns' } + } + }; + } +}); + +export function createPatterns(...ids: number[]): PatternsPayload { + return { + items: ids.map(id => + withLinks( + new PatternDto(`id${id}`, `Name${id}`, `Pattern${id}`, `Message${id}`), + { + _links: { + update: { method: 'PUT', href: `/patterns/id${id}` } + } + } + )), + _links: { + create: { method: 'POST', href: '/patterns' } + } + }; +} \ No newline at end of file diff --git a/src/Squidex/app/shared/services/patterns.service.ts b/src/Squidex/app/shared/services/patterns.service.ts index 6bd176d9c..0a81a7b76 100644 --- a/src/Squidex/app/shared/services/patterns.service.ts +++ b/src/Squidex/app/shared/services/patterns.service.ts @@ -15,22 +15,28 @@ import { ApiUrlConfig, HTTP, mapVersioned, - Model, pretifyError, + Resource, + ResourceLinks, Version, - Versioned + Versioned, + withLinks } from '@app/framework'; -export type PatternsDto = Versioned; +export type PatternsDto = Versioned; +export type PatternsPayload = { + items: PatternDto[] +} & Resource; + +export class PatternDto { + public readonly _links: ResourceLinks = {}; -export class PatternDto extends Model { constructor( public readonly id: string, public readonly name: string, public readonly pattern: string, public readonly message?: string ) { - super(); } } @@ -54,33 +60,17 @@ export class PatternsService { return HTTP.getVersioned(this.http, url).pipe( mapVersioned(({ body }) => { - const items: any[] = body; - - const patterns = - items.map(item => - new PatternDto( - item.patternId, - item.name, - item.pattern, - item.message)); - - return patterns; + return parsePatterns(body); }), pretifyError('Failed to add pattern. Please reload.')); } - public postPattern(appName: string, dto: EditPatternDto, version: Version): Observable> { + public postPattern(appName: string, dto: EditPatternDto, version: Version): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns`); return HTTP.postVersioned(this.http, url, dto, version).pipe( mapVersioned(({ body }) => { - const pattern = new PatternDto( - body.patternId, - body.name, - body.pattern, - body.message); - - return pattern; + return parsePatterns(body); }), tap(() => { this.analytics.trackEvent('Patterns', 'Created', appName); @@ -88,23 +78,48 @@ export class PatternsService { pretifyError('Failed to add pattern. Please reload.')); } - public putPattern(appName: string, id: string, dto: EditPatternDto, version: Version): Observable> { - const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns/${id}`); + public putPattern(appName: string, resource: Resource, dto: EditPatternDto, version: Version): Observable { + const link = resource._links['update']; - return HTTP.putVersioned(this.http, url, dto, version).pipe( + const url = this.apiUrl.buildUrl(link.href); + + return HTTP.requestVersioned(this.http, link.method, url, version, dto).pipe( + mapVersioned(({ body }) => { + return parsePatterns(body); + }), tap(() => { this.analytics.trackEvent('Patterns', 'Updated', appName); }), pretifyError('Failed to update pattern. Please reload.')); } - public deletePattern(appName: string, id: string, version: Version): Observable> { - const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns/${id}`); + public deletePattern(appName: string, resource: Resource, version: Version): Observable { + const link = resource._links['delete']; - return HTTP.deleteVersioned(this.http, url, version).pipe( + const url = this.apiUrl.buildUrl(link.href); + + return HTTP.requestVersioned(this.http, link.method, url, version).pipe( + mapVersioned(({ body }) => { + return parsePatterns(body); + }), tap(() => { this.analytics.trackEvent('Patterns', 'Configured', appName); }), pretifyError('Failed to remove pattern. Please reload.')); } +} + +function parsePatterns(response: any) { + const items: any[] = response.items; + + const patterns = items.map(item => + withLinks( + new PatternDto( + item.patternId, + item.name, + item.pattern, + item.message), + item)); + + return withLinks({ items: patterns, _links: {} }, response); } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/roles.service.spec.ts b/src/Squidex/app/shared/services/roles.service.spec.ts index 687e9e305..1f69b6808 100644 --- a/src/Squidex/app/shared/services/roles.service.spec.ts +++ b/src/Squidex/app/shared/services/roles.service.spec.ts @@ -11,10 +11,13 @@ import { inject, TestBed } from '@angular/core/testing'; import { AnalyticsService, ApiUrlConfig, + Resource, RoleDto, RolesDto, + RolesPayload, RolesService, - Version + Version, + withLinks } from '@app/shared/internal'; describe('RolesService', () => { @@ -70,31 +73,13 @@ describe('RolesService', () => { expect(req.request.method).toEqual('GET'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush({ - roles: [{ - name: 'Role1', - numClients: 3, - numContributors: 5, - permissions: ['P1'] - }, { - name: 'Role2', - numClients: 7, - numContributors: 9, - permissions: ['P2'] - }] - }, { + req.flush(rolesResponse(2, 4), { headers: { etag: '2' } }); - expect(roles!).toEqual({ - payload: [ - new RoleDto('Role1', 3, 5, ['P1']), - new RoleDto('Role2', 7, 9, ['P2']) - ], - version: new Version('2') - }); + expect(roles!).toEqual({ payload: createRoles(2, 4), version: new Version('2') }); })); it('should make post request to add role', @@ -102,10 +87,10 @@ describe('RolesService', () => { const dto = { name: 'Role3' }; - let role: RoleDto; + let roles: RolesDto; roleService.postRole('my-app', dto, version).subscribe(result => { - role = result.payload; + roles = result; }); const req = httpMock.expectOne('http://service/p/api/apps/my-app/roles'); @@ -113,9 +98,13 @@ describe('RolesService', () => { expect(req.request.method).toEqual('POST'); expect(req.request.headers.get('If-Match')).toEqual(version.value); - req.flush({}); + req.flush(rolesResponse(2, 4), { + headers: { + etag: '2' + } + }); - expect(role!).toEqual(new RoleDto('Role3', 0, 0, [])); + expect(roles!).toEqual({ payload: createRoles(2, 4), version: new Version('2') }); })); it('should make put request to update role', @@ -123,26 +112,92 @@ describe('RolesService', () => { const dto = { permissions: ['P4', 'P5'] }; - roleService.putRole('my-app', 'role1', dto, version).subscribe(); + const resource: Resource = { + _links: { + update: { method: 'PUT', href: '/api/apps/my-app/roles/role1' } + } + }; + + let roles: RolesDto; + + roleService.putRole('my-app', resource, dto, version).subscribe(result => { + roles = result; + }); const req = httpMock.expectOne('http://service/p/api/apps/my-app/roles/role1'); expect(req.request.method).toEqual('PUT'); expect(req.request.headers.get('If-Match')).toEqual(version.value); - req.flush({}); + req.flush(rolesResponse(2, 4), { + headers: { + etag: '2' + } + }); + + expect(roles!).toEqual({ payload: createRoles(2, 4), version: new Version('2') }); })); it('should make delete request to remove role', inject([RolesService, HttpTestingController], (roleService: RolesService, httpMock: HttpTestingController) => { - roleService.deleteRole('my-app', 'role1', version).subscribe(); + const resource: Resource = { + _links: { + delete: { method: 'DELETE', href: '/api/apps/my-app/roles/role1' } + } + }; + + let roles: RolesDto; + + roleService.deleteRole('my-app', resource, version).subscribe(result => { + roles = result; + }); const req = httpMock.expectOne('http://service/p/api/apps/my-app/roles/role1'); expect(req.request.method).toEqual('DELETE'); expect(req.request.headers.get('If-Match')).toEqual(version.value); - req.flush({}); + req.flush(rolesResponse(2, 4), { + headers: { + etag: '2' + } + }); + + expect(roles!).toEqual({ payload: createRoles(2, 4), version: new Version('2') }); })); -}); \ No newline at end of file + + function rolesResponse(...ids: number[]) { + return { + items: ids.map(id => ({ + name: `name${id}`, + numClients: id * 2, + numContributors: id * 3, + permissions: [`permission${id}`], + _links: { + update: { method: 'PUT', href: `/roles/id${id}` } + } + })), + _links: { + create: { method: 'POST', href: '/roles' } + } + }; + } +}); + +export function createRoles(...ids: number[]): RolesPayload { + return { + items: ids.map(id => + withLinks( + new RoleDto(`name${id}`, id * 2, id * 3, [`permission${id}`]), + { + _links: { + update: { method: 'PUT', href: `/roles/id${id}` } + } + } + )), + _links: { + create: { method: 'POST', href: '/roles' } + } + }; +} \ No newline at end of file diff --git a/src/Squidex/app/shared/services/roles.service.ts b/src/Squidex/app/shared/services/roles.service.ts index 5c1541897..08c4cd942 100644 --- a/src/Squidex/app/shared/services/roles.service.ts +++ b/src/Squidex/app/shared/services/roles.service.ts @@ -15,22 +15,28 @@ import { ApiUrlConfig, HTTP, mapVersioned, - Model, pretifyError, + Resource, + ResourceLinks, Version, - Versioned + Versioned, + withLinks } from '@app/framework'; -export type RolesDto = Versioned; +export type RolesDto = Versioned; +export type RolesPayload = { + items: RoleDto[] +} & Resource; + +export class RoleDto { + public readonly _links: ResourceLinks = {}; -export class RoleDto extends Model { constructor( public readonly name: string, public readonly numClients: number, public readonly numContributors: number, public readonly permissions: string[] ) { - super(); } } @@ -56,28 +62,17 @@ export class RolesService { return HTTP.getVersioned(this.http, url).pipe( mapVersioned(({ body }) => { - const items: any[] = body.roles; - - const roles = items.map(item => - new RoleDto( - item.name, - item.numClients, - item.numContributors, - item.permissions)); - - return roles; + return parseRoles(body); }), pretifyError('Failed to load roles. Please reload.')); } - public postRole(appName: string, dto: CreateRoleDto, version: Version): Observable> { + public postRole(appName: string, dto: CreateRoleDto, version: Version): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/roles`); return HTTP.postVersioned(this.http, url, dto, version).pipe( - mapVersioned(() => { - const role = new RoleDto(dto.name, 0, 0, []); - - return role; + mapVersioned(({ body }) => { + return parseRoles(body); }), tap(() => { this.analytics.trackEvent('Role', 'Created', appName); @@ -85,20 +80,30 @@ export class RolesService { pretifyError('Failed to add role. Please reload.')); } - public putRole(appName: string, name: string, dto: UpdateRoleDto, version: Version): Observable> { - const url = this.apiUrl.buildUrl(`api/apps/${appName}/roles/${name}`); + public putRole(appName: string, resource: Resource, dto: UpdateRoleDto, version: Version): Observable { + const link = resource._links['update']; + + const url = this.apiUrl.buildUrl(link.href); - return HTTP.putVersioned(this.http, url, dto, version).pipe( + return HTTP.requestVersioned(this.http, link.method, url, version, dto).pipe( + mapVersioned(({ body }) => { + return parseRoles(body); + }), tap(() => { this.analytics.trackEvent('Role', 'Updated', appName); }), pretifyError('Failed to revoke role. Please reload.')); } - public deleteRole(appName: string, name: string, version: Version): Observable> { - const url = this.apiUrl.buildUrl(`api/apps/${appName}/roles/${name}`); + public deleteRole(appName: string, resource: Resource, version: Version): Observable { + const link = resource._links['delete']; - return HTTP.deleteVersioned(this.http, url, version).pipe( + const url = this.apiUrl.buildUrl(link.href); + + return HTTP.requestVersioned(this.http, link.method, url, version).pipe( + mapVersioned(({ body }) => { + return parseRoles(body); + }), tap(() => { this.analytics.trackEvent('Role', 'Deleted', appName); }), @@ -111,4 +116,19 @@ export class RolesService { return this.http.get(url).pipe( pretifyError('Failed to load permissions. Please reload.')); } +} + +export function parseRoles(response: any) { + const items: any[] = response.items; + + const roles = items.map(item => + withLinks( + new RoleDto( + item.name, + item.numClients, + item.numContributors, + item.permissions), + item)); + + return withLinks({ items: roles, _links: {} }, response); } \ No newline at end of file diff --git a/src/Squidex/app/shared/state/clients.state.spec.ts b/src/Squidex/app/shared/state/clients.state.spec.ts index 97f278375..ef3b902d2 100644 --- a/src/Squidex/app/shared/state/clients.state.spec.ts +++ b/src/Squidex/app/shared/state/clients.state.spec.ts @@ -9,6 +9,7 @@ import { of } from 'rxjs'; import { IMock, It, Mock, Times } from 'typemoq'; import { + ClientsPayload, ClientsService, ClientsState, DialogService, @@ -88,8 +89,7 @@ describe('ClientsState', () => { clientsState.attach(request).subscribe(); - expect(clientsState.snapshot.clients.values).toEqual(updated.items); - expect(clientsState.snapshot.version).toEqual(newVersion); + expectNewClients(updated); }); it('should update clients when role updated', () => { @@ -97,13 +97,12 @@ describe('ClientsState', () => { const request = { role: 'Owner' }; - clientsService.setup(x => x.putClient(app, oldClients[0].id, request, version)) - .returns(() => of(versioned(newVersion))).verifiable(); + clientsService.setup(x => x.putClient(app, oldClients.items[0], request, version)) + .returns(() => of(versioned(newVersion, updated))).verifiable(); - clientsState.update(oldClients[0], request).subscribe(); + clientsState.update(oldClients.items[0], request).subscribe(); - expect(clientsState.snapshot.clients.values).toEqual(updated.items); - expect(clientsState.snapshot.version).toEqual(newVersion); + expectNewClients(updated); }); it('should update clients when name updated', () => { @@ -112,24 +111,28 @@ describe('ClientsState', () => { const request = { name: 'NewName' }; clientsService.setup(x => x.putClient(app, oldClients.items[0], request, version)) - .returns(() => of(versioned(newVersion))).verifiable(); + .returns(() => of(versioned(newVersion, updated))).verifiable(); - clientsState.update(oldClients[0], request).subscribe(); + clientsState.update(oldClients.items[0], request).subscribe(); - expect(clientsState.snapshot.clients.values).toEqual(updated.items); - expect(clientsState.snapshot.version).toEqual(newVersion); + expectNewClients(updated); }); it('should update clients when client revoked', () => { const updated = createClients(1, 2, 3); clientsService.setup(x => x.deleteClient(app, oldClients.items[0], version)) - .returns(() => of(versioned(newVersion))).verifiable(); + .returns(() => of(versioned(newVersion, updated))).verifiable(); - clientsState.revoke(oldClients[0]).subscribe(); + clientsState.revoke(oldClients.items[0]).subscribe(); + expectNewClients(updated); + }); + + function expectNewClients(updated: ClientsPayload) { expect(clientsState.snapshot.clients.values).toEqual(updated.items); expect(clientsState.snapshot.version).toEqual(newVersion); - }); + + } }); }); \ No newline at end of file diff --git a/src/Squidex/app/shared/state/clients.state.ts b/src/Squidex/app/shared/state/clients.state.ts index b7f6706d4..c7c034db5 100644 --- a/src/Squidex/app/shared/state/clients.state.ts +++ b/src/Squidex/app/shared/state/clients.state.ts @@ -65,7 +65,7 @@ export class ClientsState extends State { private readonly appsState: AppsState, private readonly dialogs: DialogService ) { - super({ clients: ImmutableArray.empty(), version: new Version(''), links: {} }); + super({ clients: ImmutableArray.empty(), version: Version.EMPTY, links: {} }); } public load(isReload = false): Observable { diff --git a/src/Squidex/app/shared/state/contributors.state.spec.ts b/src/Squidex/app/shared/state/contributors.state.spec.ts index bc693c358..03e528aa2 100644 --- a/src/Squidex/app/shared/state/contributors.state.spec.ts +++ b/src/Squidex/app/shared/state/contributors.state.spec.ts @@ -9,6 +9,7 @@ import { of } from 'rxjs'; import { IMock, It, Mock, Times } from 'typemoq'; import { + ContributorsPayload, ContributorsService, ContributorsState, DialogService, @@ -81,7 +82,7 @@ describe('ContributorsState', () => { }); it('should update contributors when user assigned', () => { - const updated = createContributors(1, 2, 3); + const updated = createContributors(5, 6); const request = { contributorId: 'mail2stehle@gmail.com', role: 'Developer' }; @@ -90,22 +91,24 @@ describe('ContributorsState', () => { contributorsState.assign(request).subscribe(); - expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.items); - expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors); - expect(contributorsState.snapshot.version).toEqual(newVersion); + expectNewContributors(updated); }); it('should update contributors when contribution revoked', () => { - const updated = createContributors(1, 2, 3); + const updated = createContributors(5, 6); contributorsService.setup(x => x.deleteContributor(app, oldContributors.items[0], version)) .returns(() => of(versioned(newVersion, updated))).verifiable(); contributorsState.revoke(oldContributors.items[0]).subscribe(); - expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.items); - expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors); - expect(contributorsState.snapshot.version).toEqual(newVersion); + expectNewContributors(updated); }); + + function expectNewContributors(updated: ContributorsPayload) { + expect(contributorsState.snapshot.contributors.values).toEqual(updated.items); + expect(contributorsState.snapshot.maxContributors).toBe(updated.maxContributors); + expect(contributorsState.snapshot.version).toEqual(newVersion); + } }); }); \ No newline at end of file diff --git a/src/Squidex/app/shared/state/contributors.state.ts b/src/Squidex/app/shared/state/contributors.state.ts index 6af56fff8..e50d341e6 100644 --- a/src/Squidex/app/shared/state/contributors.state.ts +++ b/src/Squidex/app/shared/state/contributors.state.ts @@ -79,7 +79,7 @@ export class ContributorsState extends State { private readonly appsState: AppsState, private readonly dialogs: DialogService ) { - super({ contributors: ImmutableArray.empty(), version: new Version(''), maxContributors: -1, links: {} }); + super({ contributors: ImmutableArray.empty(), version: Version.EMPTY, maxContributors: -1, links: {} }); } public load(isReload = false): Observable { diff --git a/src/Squidex/app/shared/state/languages.state.spec.ts b/src/Squidex/app/shared/state/languages.state.spec.ts index 96d451015..cdbf7662d 100644 --- a/src/Squidex/app/shared/state/languages.state.spec.ts +++ b/src/Squidex/app/shared/state/languages.state.spec.ts @@ -109,7 +109,7 @@ describe('LanguagesState', () => { languagesState.add(languageIT).subscribe(); - expectUpdated(updated); + expectNewLanguages(updated); }); it('should update languages when language updated', () => { @@ -122,7 +122,7 @@ describe('LanguagesState', () => { languagesState.update(oldLanguages.items[1], request).subscribe(); - expectUpdated(updated); + expectNewLanguages(updated); }); it('should update languages when language deleted', () => { @@ -133,10 +133,10 @@ describe('LanguagesState', () => { languagesState.remove(oldLanguages.items[1]).subscribe(); - expectUpdated(updated); + expectNewLanguages(updated); }); - function expectUpdated(updated: AppLanguagesPayload) { + function expectNewLanguages(updated: AppLanguagesPayload) { expect(languagesState.snapshot.languages.values).toEqual([ { language: updated.items[0], diff --git a/src/Squidex/app/shared/state/languages.state.ts b/src/Squidex/app/shared/state/languages.state.ts index 00ff0806b..5c7d41686 100644 --- a/src/Squidex/app/shared/state/languages.state.ts +++ b/src/Squidex/app/shared/state/languages.state.ts @@ -96,7 +96,7 @@ export class LanguagesState extends State { allLanguages: ImmutableArray.empty(), allLanguagesNew: ImmutableArray.empty(), languages: ImmutableArray.empty(), - version: new Version(''), + version: Version.EMPTY, links: {} }); } diff --git a/src/Squidex/app/shared/state/patterns.state.spec.ts b/src/Squidex/app/shared/state/patterns.state.spec.ts index b0de52e42..5b6eb2016 100644 --- a/src/Squidex/app/shared/state/patterns.state.spec.ts +++ b/src/Squidex/app/shared/state/patterns.state.spec.ts @@ -10,12 +10,14 @@ import { IMock, It, Mock, Times } from 'typemoq'; import { DialogService, - PatternDto, + PatternsPayload, PatternsService, PatternsState, versioned } from '@app/shared/internal'; +import { createPatterns } from '../services/patterns.service.spec'; + import { TestValues } from './_test-helpers'; describe('PatternsState', () => { @@ -26,10 +28,7 @@ describe('PatternsState', () => { version } = TestValues; - const oldPatterns = [ - new PatternDto('id1', 'name1', 'pattern1', ''), - new PatternDto('id2', 'name2', 'pattern2', '') - ]; + const oldPatterns = createPatterns(1, 2, 3); let dialogs: IMock; let patternsService: IMock; @@ -49,11 +48,11 @@ describe('PatternsState', () => { describe('Loading', () => { it('should load patterns', () => { patternsService.setup(x => x.getPatterns(app)) - .returns(() => of({ payload: oldPatterns, version })).verifiable(); + .returns(() => of(versioned(version, oldPatterns))).verifiable(); patternsState.load().subscribe(); - expect(patternsState.snapshot.patterns.values).toEqual(oldPatterns); + expect(patternsState.snapshot.patterns.values).toEqual(oldPatterns.items); expect(patternsState.snapshot.version).toEqual(version); dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never()); @@ -61,7 +60,7 @@ describe('PatternsState', () => { it('should show notification on load when reload is true', () => { patternsService.setup(x => x.getPatterns(app)) - .returns(() => of({ payload: oldPatterns, version })).verifiable(); + .returns(() => of(versioned(version, oldPatterns))).verifiable(); patternsState.load(true).subscribe(); @@ -74,49 +73,51 @@ describe('PatternsState', () => { describe('Updates', () => { beforeEach(() => { patternsService.setup(x => x.getPatterns(app)) - .returns(() => of({ payload: oldPatterns, version })).verifiable(); + .returns(() => of(versioned(version, oldPatterns))).verifiable(); patternsState.load().subscribe(); }); it('should add pattern to snapshot when created', () => { - const newPattern = new PatternDto('id3', 'name3', 'pattern3', ''); + const updated = createPatterns(4, 5); - const request = { ...newPattern }; + const request = { name: 'new', pattern: 'a-z' }; patternsService.setup(x => x.postPattern(app, request, version)) - .returns(() => of(versioned(newVersion, newPattern))).verifiable(); + .returns(() => of(versioned(newVersion, updated))).verifiable(); patternsState.create(request).subscribe(); - expect(patternsState.snapshot.patterns.values).toEqual([...oldPatterns, newPattern]); - expect(patternsState.snapshot.version).toEqual(newVersion); + expectNewPatterns(updated); }); it('should update properties when updated', () => { - const request = { name: 'name2_1', pattern: 'pattern2_1', message: 'message2_1' }; + const updated = createPatterns(4, 5); - patternsService.setup(x => x.putPattern(app, oldPatterns[1].id, request, version)) - .returns(() => of(versioned(newVersion))).verifiable(); + const request = { name: 'name2_1', pattern: 'pattern2_1', message: 'message2_1' }; - patternsState.update(oldPatterns[1], request).subscribe(); + patternsService.setup(x => x.putPattern(app, oldPatterns.items[1], request, version)) + .returns(() => of(versioned(newVersion, updated))).verifiable(); - const pattern_1 = patternsState.snapshot.patterns.at(1); + patternsState.update(oldPatterns.items[1], request).subscribe(); - expect(pattern_1.name).toBe(request.name); - expect(pattern_1.pattern).toBe(request.pattern); - expect(pattern_1.message).toBe(request.message); - expect(patternsState.snapshot.version).toEqual(newVersion); + expectNewPatterns(updated); }); it('should remove pattern from snapshot when deleted', () => { - patternsService.setup(x => x.deletePattern(app, oldPatterns[0].id, version)) - .returns(() => of(versioned(newVersion))).verifiable(); + const updated = createPatterns(4, 5); - patternsState.delete(oldPatterns[0]).subscribe(); + patternsService.setup(x => x.deletePattern(app, oldPatterns.items[0], version)) + .returns(() => of(versioned(newVersion, updated))).verifiable(); - expect(patternsState.snapshot.patterns.values).toEqual([oldPatterns[1]]); - expect(patternsState.snapshot.version).toEqual(newVersion); + patternsState.delete(oldPatterns.items[0]).subscribe(); + + expectNewPatterns(updated); }); + + function expectNewPatterns(updated: PatternsPayload) { + expect(patternsState.snapshot.patterns.values).toEqual(updated.items); + expect(patternsState.snapshot.version).toEqual(newVersion); + } }); }); \ No newline at end of file diff --git a/src/Squidex/app/shared/state/patterns.state.ts b/src/Squidex/app/shared/state/patterns.state.ts index d1c5afbd8..5e9b067ba 100644 --- a/src/Squidex/app/shared/state/patterns.state.ts +++ b/src/Squidex/app/shared/state/patterns.state.ts @@ -12,7 +12,7 @@ import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { DialogService, ImmutableArray, - mapVersioned, + ResourceLinks, shareMapSubscribed, shareSubscribed, State, @@ -24,6 +24,7 @@ import { AppsState } from './apps.state'; import { EditPatternDto, PatternDto, + PatternsPayload, PatternsService } from './../services/patterns.service'; @@ -36,6 +37,9 @@ interface Snapshot { // Indicates if the patterns are loaded. isLoaded?: boolean; + + // The links. + links: ResourceLinks; } type PatternsList = ImmutableArray; @@ -55,7 +59,7 @@ export class PatternsState extends State { private readonly appsState: AppsState, private readonly dialogs: DialogService ) { - super({ patterns: ImmutableArray.empty(), version: new Version('') }); + super({ patterns: ImmutableArray.empty(), version: Version.EMPTY, links: {} }); } public load(isReload = false): Observable { @@ -69,52 +73,43 @@ export class PatternsState extends State { this.dialogs.notifyInfo('Patterns reloaded.'); } - this.next(s => { - const patterns = ImmutableArray.of(payload).sortByStringAsc(x => x.name); - - return { ...s, patterns, isLoaded: true, version: version }; - }); + this.replacePatterns(payload, version); }), shareMapSubscribed(this.dialogs, x => x.payload)); } - public create(request: EditPatternDto): Observable { + public create(request: EditPatternDto): Observable { return this.patternsService.postPattern(this.appName, request, this.version).pipe( tap(({ version, payload }) => { - this.next(s => { - const patterns = s.patterns.push(payload).sortByStringAsc(x => x.name); - - return { ...s, patterns, version: version }; - }); + this.replacePatterns(payload, version); }), - shareMapSubscribed(this.dialogs, x => x.payload)); + shareSubscribed(this.dialogs)); } - public update(pattern: PatternDto, request: EditPatternDto): Observable { - return this.patternsService.putPattern(this.appName, pattern.id, request, this.version).pipe( - mapVersioned(() => update(pattern, request)), + public update(pattern: PatternDto, request: EditPatternDto): Observable { + return this.patternsService.putPattern(this.appName, pattern, request, this.version).pipe( tap(({ version, payload }) => { - this.next(s => { - const patterns = s.patterns.replaceBy('id', payload).sortByStringAsc(x => x.name); - - return { ...s, patterns, version: version }; - }); + this.replacePatterns(payload, version); }), - shareMapSubscribed(this.dialogs, x => x.payload)); + shareSubscribed(this.dialogs)); } public delete(pattern: PatternDto): Observable { - return this.patternsService.deletePattern(this.appName, pattern.id, this.version).pipe( - tap(({ version }) => { - this.next(s => { - const patterns = s.patterns.filter(c => c.id !== pattern.id); - - return { ...s, patterns, version: version }; - }); + return this.patternsService.deletePattern(this.appName, pattern, this.version).pipe( + tap(({ version, payload }) => { + this.replacePatterns(payload, version); }), shareSubscribed(this.dialogs)); } + private replacePatterns(payload: PatternsPayload, version: Version) { + const patterns = ImmutableArray.of(payload.items); + + this.next(s => { + return { ...s, patterns, isLoaded: true, version, links: payload._links }; + }); + } + private get appName() { return this.appsState.appName; } @@ -122,7 +117,4 @@ export class PatternsState extends State { private get version() { return this.snapshot.version; } -} - -const update = (pattern: PatternDto, request: EditPatternDto) => - pattern.with(request); \ No newline at end of file +} \ No newline at end of file diff --git a/src/Squidex/app/shared/state/plans.state.ts b/src/Squidex/app/shared/state/plans.state.ts index d1fcb0ba9..f6857342e 100644 --- a/src/Squidex/app/shared/state/plans.state.ts +++ b/src/Squidex/app/shared/state/plans.state.ts @@ -79,7 +79,7 @@ export class PlansState extends State { private readonly dialogs: DialogService, private readonly plansService: PlansService ) { - super({ plans: ImmutableArray.empty(), version: new Version('') }); + super({ plans: ImmutableArray.empty(), version: Version.EMPTY }); } public load(isReload = false, overridePlanId?: string): Observable { diff --git a/src/Squidex/app/shared/state/roles.state.spec.ts b/src/Squidex/app/shared/state/roles.state.spec.ts index d3326a6f5..6cf0a6345 100644 --- a/src/Squidex/app/shared/state/roles.state.spec.ts +++ b/src/Squidex/app/shared/state/roles.state.spec.ts @@ -10,12 +10,14 @@ import { IMock, It, Mock, Times } from 'typemoq'; import { DialogService, - RoleDto, + RolesPayload, RolesService, RolesState, versioned } from '@app/shared/internal'; +import { createRoles } from '../services/roles.service.spec'; + import { TestValues } from './_test-helpers'; describe('RolesState', () => { @@ -26,10 +28,7 @@ describe('RolesState', () => { version } = TestValues; - const oldRoles = [ - new RoleDto('Role1', 3, 5, ['P1']), - new RoleDto('Role2', 7, 9, ['P2']) - ]; + const oldRoles = createRoles(1, 2); let dialogs: IMock; let rolesService: IMock; @@ -45,11 +44,11 @@ describe('RolesState', () => { describe('Loading', () => { it('should load roles', () => { rolesService.setup(x => x.getRoles(app)) - .returns(() => of({ payload: oldRoles, version })).verifiable(); + .returns(() => of(versioned(version, oldRoles))).verifiable(); rolesState.load().subscribe(); - expect(rolesState.snapshot.roles.values).toEqual(oldRoles); + expect(rolesState.snapshot.roles.values).toEqual(oldRoles.items); expect(rolesState.snapshot.isLoaded).toBeTruthy(); expect(rolesState.snapshot.version).toEqual(version); @@ -76,42 +75,46 @@ describe('RolesState', () => { rolesState.load().subscribe(); }); - it('should add role to snapshot when added', () => { - const newRole = new RoleDto('Role3', 0, 0, ['P3']); + it('should update roles when role added', () => { + const updated = createRoles(4, 5); - const request = { name: newRole.name }; + const request = { name: 'newRole' }; rolesService.setup(x => x.postRole(app, request, version)) - .returns(() => of(versioned(newVersion, newRole))); + .returns(() => of(versioned(newVersion, updated))); rolesState.add(request).subscribe(); - expect(rolesState.snapshot.roles.values).toEqual([oldRoles[0], oldRoles[1], newRole]); - expect(rolesState.snapshot.version).toEqual(newVersion); + expectNewRoles(updated); }); - it('should update permissions when updated', () => { - const request = { permissions: ['P4', 'P5'] }; + it('should update roles when role updated', () => { + const updated = createRoles(4, 5); - rolesService.setup(x => x.putRole(app, oldRoles[1].name, request, version)) - .returns(() => of(versioned(newVersion))); + const request = { permissions: ['P4', 'P5'] }; - rolesState.update(oldRoles[1], request).subscribe(); + rolesService.setup(x => x.putRole(app, oldRoles.items[1], request, version)) + .returns(() => of(versioned(newVersion, updated))); - const role_1 = rolesState.snapshot.roles.at(1); + rolesState.update(oldRoles.items[1], request).subscribe(); - expect(role_1.permissions).toEqual(request.permissions); - expect(rolesState.snapshot.version).toEqual(newVersion); + expectNewRoles(updated); }); - it('should remove role from snapshot when deleted', () => { - rolesService.setup(x => x.deleteRole(app, oldRoles[0].name, version)) - .returns(() => of(versioned(newVersion))); + it('should update roles when role deleted', () => { + const updated = createRoles(4, 5); - rolesState.delete(oldRoles[0]).subscribe(); + rolesService.setup(x => x.deleteRole(app, oldRoles.items[1], version)) + .returns(() => of(versioned(newVersion, updated))); - expect(rolesState.snapshot.roles.values).toEqual([oldRoles[1]]); - expect(rolesState.snapshot.version).toEqual(newVersion); + rolesState.delete(oldRoles.items[1]).subscribe(); + + expectNewRoles(updated); }); + + function expectNewRoles(updated: RolesPayload) { + expect(rolesState.snapshot.roles.values).toEqual(updated.items); + expect(rolesState.snapshot.version).toEqual(newVersion); + } }); -}); \ No newline at end of file +}); diff --git a/src/Squidex/app/shared/state/roles.state.ts b/src/Squidex/app/shared/state/roles.state.ts index d4b924e01..b5f15d347 100644 --- a/src/Squidex/app/shared/state/roles.state.ts +++ b/src/Squidex/app/shared/state/roles.state.ts @@ -12,8 +12,7 @@ import { distinctUntilChanged, map, tap } from 'rxjs/operators'; import { DialogService, ImmutableArray, - mapVersioned, - shareMapSubscribed, + ResourceLinks, shareSubscribed, State, Version @@ -24,6 +23,7 @@ import { AppsState } from './apps.state'; import { CreateRoleDto, RoleDto, + RolesPayload, RolesService, UpdateRoleDto } from './../services/roles.service'; @@ -37,6 +37,9 @@ interface Snapshot { // Indicates if the roles are loaded. isLoaded?: boolean; + + // The links. + links: ResourceLinks; } type RolesList = ImmutableArray; @@ -56,7 +59,7 @@ export class RolesState extends State { private readonly appsState: AppsState, private readonly dialogs: DialogService ) { - super({ roles: ImmutableArray.empty(), version: new Version('') }); + super({ roles: ImmutableArray.empty(), version: Version.EMPTY, links: {} }); } public load(isReload = false): Observable { @@ -70,50 +73,41 @@ export class RolesState extends State { this.dialogs.notifyInfo('Roles reloaded.'); } - this.next(s => { - const roles = ImmutableArray.of(payload).sortByStringAsc(x => x.name); - - return { ...s, roles, isLoaded: true, version }; - }); + this.replaceRoles(payload, version); }), shareSubscribed(this.dialogs)); } - public add(request: CreateRoleDto): Observable { + public add(request: CreateRoleDto): Observable { return this.rolesService.postRole(this.appName, request, this.version).pipe( tap(({ version, payload }) => { - this.next(s => { - const roles = s.roles.push(payload).sortByStringAsc(x => x.name); + this.replaceRoles(payload, version); + }), + shareSubscribed(this.dialogs)); + } - return { ...s, roles, version }; - }); + public update(role: RoleDto, request: UpdateRoleDto): Observable { + return this.rolesService.putRole(this.appName, role, request, this.version).pipe( + tap(({ version, payload }) => { + this.replaceRoles(payload, version); }), - shareMapSubscribed(this.dialogs, x => x.payload)); + shareSubscribed(this.dialogs)); } public delete(role: RoleDto): Observable { - return this.rolesService.deleteRole(this.appName, role.name, this.version).pipe( - tap(({ version }) => { - this.next(s => { - const roles = s.roles.removeBy('name', role); - - return { ...s, roles, version }; - }); + return this.rolesService.deleteRole(this.appName, role, this.version).pipe( + tap(({ version, payload }) => { + this.replaceRoles(payload, version); }), shareSubscribed(this.dialogs)); } - public update(role: RoleDto, request: UpdateRoleDto): Observable { - return this.rolesService.putRole(this.appName, role.name, request, this.version).pipe( - mapVersioned(() => update(role, request)), - tap(({ version, payload }) => { - this.next(s => { - const roles = s.roles.replaceBy('name', payload); + private replaceRoles(payload: RolesPayload, version: Version) { + const roles = ImmutableArray.of(payload.items); - return { ...s, roles, version }; - }); - }), - shareMapSubscribed(this.dialogs, x => x.payload)); + this.next(s => { + return { ...s, roles, isLoaded: true, version, links: payload._links }; + }); } private get appName() { @@ -123,7 +117,4 @@ export class RolesState extends State { private get version() { return this.snapshot.version; } -} - -const update = (role: RoleDto, request: UpdateRoleDto) => - role.with({ permissions: request.permissions }); \ No newline at end of file +} \ No newline at end of file