diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs
index a17673252..af59c84ae 100644
--- a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs
+++ b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs
@@ -10,6 +10,7 @@ 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;
@@ -41,12 +42,12 @@ namespace Squidex.Areas.Api.Controllers.Apps
///
[HttpGet]
[Route("apps/{app}/clients/")]
- [ProducesResponseType(typeof(ClientDto[]), 200)]
+ [ProducesResponseType(typeof(ClientsDto), 200)]
[ApiPermission(Permissions.AppClientsRead)]
[ApiCosts(0)]
public IActionResult GetClients(string app)
{
- var response = App.Clients.Select(ClientDto.FromKvp).ToArray();
+ var response = ClientsDto.FromApp(App, this);
Response.Headers[HeaderNames.ETag] = App.Version.ToString();
@@ -60,6 +61,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// Client object that needs to be added to the app.
///
/// 201 => Client generated.
+ /// 400 => Client request not valid.
/// 404 => App not found.
///
///
@@ -68,16 +70,15 @@ namespace Squidex.Areas.Api.Controllers.Apps
///
[HttpPost]
[Route("apps/{app}/clients/")]
- [ProducesResponseType(typeof(ClientDto), 201)]
+ [ProducesResponseType(typeof(ClientsDto), 200)]
+ [ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppClientsCreate)]
[ApiCosts(1)]
public async Task PostClient(string app, [FromBody] CreateClientDto request)
{
var command = request.ToCommand();
- await CommandBus.PublishAsync(command);
-
- var response = ClientDto.FromCommand(command);
+ var response = await InvokeCommandAsync(command);
return CreatedAtAction(nameof(GetClients), new { app }, response);
}
@@ -89,7 +90,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// The id of the client that must be updated.
/// Client object that needs to be updated.
///
- /// 204 => Client updated.
+ /// 200 => Client updated.
/// 400 => Client request not valid.
/// 404 => Client or app not found.
///
@@ -98,13 +99,17 @@ namespace Squidex.Areas.Api.Controllers.Apps
///
[HttpPut]
[Route("apps/{app}/clients/{clientId}/")]
+ [ProducesResponseType(typeof(ClientsDto), 200)]
+ [ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppClientsUpdate)]
[ApiCosts(1)]
public async Task PutClient(string app, string clientId, [FromBody] UpdateClientDto request)
{
- await CommandBus.PublishAsync(request.ToCommand(clientId));
+ var command = request.ToCommand(clientId);
+
+ var response = await InvokeCommandAsync(command);
- return NoContent();
+ return Ok(response);
}
///
@@ -113,7 +118,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// The name of the app.
/// The id of the client that must be deleted.
///
- /// 204 => Client revoked.
+ /// 200 => Client revoked.
/// 404 => Client or app not found.
///
///
@@ -121,13 +126,26 @@ namespace Squidex.Areas.Api.Controllers.Apps
///
[HttpDelete]
[Route("apps/{app}/clients/{clientId}/")]
+ [ProducesResponseType(typeof(ClientsDto), 200)]
[ApiPermission(Permissions.AppClientsDelete)]
[ApiCosts(1)]
public async Task DeleteClient(string app, string clientId)
{
- await CommandBus.PublishAsync(new RevokeClient { Id = clientId });
+ var command = new RevokeClient { Id = clientId };
+
+ 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 = ClientsDto.FromApp(result, this);
- return NoContent();
+ return response;
}
}
}
diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs
index 79769db43..d3922eae7 100644
--- a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs
+++ b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs
@@ -109,11 +109,20 @@ namespace Squidex.Areas.Api.Controllers.Apps
public async Task DeleteContributor(string app, string id)
{
var command = new RemoveContributor { ContributorId = id };
- var context = await CommandBus.PublishAsync(command);
- var response = ContributorsDto.FromApp(context.Result(), appPlansProvider, this, false);
+ 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 = ContributorsDto.FromApp(result, appPlansProvider, this, false);
+
+ return response;
+ }
}
}
diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs
index ba55d5485..ed24f37bd 100644
--- a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs
+++ b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs
@@ -72,7 +72,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
{
var command = request.ToCommand();
- var response = await InvokeCommandAsync(app, command);
+ var response = await InvokeCommandAsync(command);
return CreatedAtAction(nameof(GetLanguages), new { app }, response);
}
@@ -98,7 +98,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
{
var command = request.ToCommand(ParseLanguage(language));
- var response = await InvokeCommandAsync(app, command);
+ var response = await InvokeCommandAsync(command);
return Ok(response);
}
@@ -121,12 +121,12 @@ namespace Squidex.Areas.Api.Controllers.Apps
{
var command = new RemoveLanguage { Language = ParseLanguage(language) };
- var response = await InvokeCommandAsync(app, command);
+ var response = await InvokeCommandAsync(command);
return Ok(response);
}
- private async Task InvokeCommandAsync(string app, ICommand command)
+ private async Task InvokeCommandAsync(ICommand command)
{
var context = await CommandBus.PublishAsync(command);
diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
index 36f336ced..0cf66505f 100644
--- a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
+++ b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
@@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using Squidex.Areas.Api.Controllers.Apps.Models;
using Squidex.Domain.Apps.Entities;
+using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure;
@@ -84,7 +85,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
///
[HttpPost]
[Route("apps/")]
- [ProducesResponseType(typeof(AppCreatedDto), 201)]
+ [ProducesResponseType(typeof(AppDto), 201)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ProducesResponseType(typeof(ErrorDto), 409)]
[ApiPermission]
@@ -93,8 +94,11 @@ namespace Squidex.Areas.Api.Controllers.Apps
{
var context = await CommandBus.PublishAsync(request.ToCommand());
- var result = context.Result>();
- var response = AppCreatedDto.FromResult(request.Name, result, appPlansProvider);
+ var userOrClientId = HttpContext.User.UserOrClientId();
+ var userPermissions = HttpContext.Permissions();
+
+ var result = context.Result();
+ var response = AppDto.FromApp(result, userOrClientId, userPermissions, appPlansProvider, this);
return CreatedAtAction(nameof(GetApps), response);
}
diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppCreatedDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppCreatedDto.cs
deleted file mode 100644
index 38e6adae1..000000000
--- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppCreatedDto.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-// ==========================================================================
-// Squidex Headless CMS
-// ==========================================================================
-// Copyright (c) Squidex UG (haftungsbeschränkt)
-// All rights reserved. Licensed under the MIT license.
-// ==========================================================================
-
-using System;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using Squidex.Domain.Apps.Core.Apps;
-using Squidex.Domain.Apps.Entities.Apps.Services;
-using Squidex.Infrastructure.Commands;
-
-namespace Squidex.Areas.Api.Controllers.Apps.Models
-{
- public sealed class AppCreatedDto
- {
- ///
- /// Id of the created entity.
- ///
- [Required]
- public string Id { get; set; }
-
- ///
- /// The permission level of the user.
- ///
- public string[] Permissions { get; set; }
-
- ///
- /// The new version of the entity.
- ///
- public long Version { get; set; }
-
- ///
- /// Gets the current plan name.
- ///
- public string PlanName { get; set; }
-
- ///
- /// Gets the next plan name.
- ///
- public string PlanUpgrade { get; set; }
-
- public static AppCreatedDto FromResult(string name, EntityCreatedResult result, IAppPlansProvider apps)
- {
- var response = new AppCreatedDto
- {
- Id = result.IdOrValue.ToString(),
- Permissions = Role.CreateOwner(name).Permissions.ToIds().ToArray(),
- PlanName = apps.GetPlan(null)?.Name,
- PlanUpgrade = apps.GetPlanUpgrade(null)?.Name,
- Version = result.Version
- };
-
- return response;
- }
- }
-}
diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
index a41ad2dd0..13b726623 100644
--- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
+++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
@@ -8,9 +8,11 @@
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;
+using Squidex.Areas.Api.Controllers.Ping;
using Squidex.Areas.Api.Controllers.Plans;
using Squidex.Areas.Api.Controllers.Rules;
using Squidex.Areas.Api.Controllers.Schemas;
@@ -59,6 +61,16 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
///
public string[] Permissions { get; set; }
+ ///
+ /// Indicates if the user can access the api.
+ ///
+ public bool CanAccessApi { get; set; }
+
+ ///
+ /// Indicates if the user can access at least one content.
+ ///
+ public bool CanAccessContent { get; set; }
+
///
/// Gets the current plan name.
///
@@ -70,6 +82,26 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
public string PlanUpgrade { get; set; }
public static AppDto FromApp(IAppEntity app, string userId, PermissionSet userPermissions, IAppPlansProvider plans, ApiController controller)
+ {
+ var permissions = GetPermissions(app, userId, userPermissions);
+
+ var result = SimpleMapper.Map(app, new AppDto());
+
+ result.Permissions = permissions.ToIds().ToArray();
+ result.PlanName = plans.GetPlanForApp(app)?.Name;
+
+ result.CanAccessApi = controller.HasPermission(AllPermissions.AppApi, app.Name, "*", permissions);
+ result.CanAccessContent = controller.HasPermission(AllPermissions.AppContentsRead, app.Name, "*", permissions);
+
+ if (controller.HasPermission(AllPermissions.AppPlansChange, app.Name))
+ {
+ result.PlanUpgrade = plans.GetPlanUpgradeForApp(app)?.Name;
+ }
+
+ return CreateLinks(result, controller, permissions);
+ }
+
+ private static PermissionSet GetPermissions(IAppEntity app, string userId, PermissionSet userPermissions)
{
var permissions = new List();
@@ -83,23 +115,15 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
permissions.AddRange(userPermissions.ToAppPermissions(app.Name));
}
- var result = SimpleMapper.Map(app, new AppDto());
-
- result.Permissions = permissions.ToArray(x => x.Id);
- result.PlanName = plans.GetPlanForApp(app)?.Name;
-
- if (controller.HasPermission(AllPermissions.AppPlansChange, app.Name))
- {
- result.PlanUpgrade = plans.GetPlanUpgradeForApp(app)?.Name;
- }
-
- return CreateLinks(result, controller, new PermissionSet(permissions));
+ return new PermissionSet(permissions);
}
private static AppDto CreateLinks(AppDto result, ApiController controller, PermissionSet permissions)
{
var values = new { app = result.Name };
+ result.AddGetLink("ping", controller.Url(x => nameof(x.GetAppPing), values));
+
if (controller.HasPermission(AllPermissions.AppDelete, result.Name, permissions: permissions))
{
result.AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteApp), values));
diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs
index 75d6b8da3..70c4e3649 100644
--- a/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs
+++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs
@@ -5,16 +5,14 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
-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 Roles = Squidex.Domain.Apps.Core.Apps.Role;
+using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
- public sealed class ClientDto
+ public sealed class ClientDto : Resource
{
///
/// The client id.
@@ -39,14 +37,16 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
///
public string Role { get; set; }
- public static ClientDto FromKvp(KeyValuePair kvp)
+ public static ClientDto FromClient(string id, AppClient client, ApiController controller, string app)
{
- return SimpleMapper.Map(kvp.Value, new ClientDto { Id = kvp.Key });
+ var result = SimpleMapper.Map(client, new ClientDto { Id = id });
+
+ return CreateLinks(result, controller, app);
}
- public static ClientDto FromCommand(AttachClient command)
+ private static ClientDto CreateLinks(ClientDto result, ApiController controller, string app)
{
- return SimpleMapper.Map(command, new ClientDto { Name = command.Id, Role = Roles.Editor });
+ return result;
}
}
}
diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs
new file mode 100644
index 000000000..cf92b47ac
--- /dev/null
+++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.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 ClientsDto : Resource
+ {
+ ///
+ /// The clients.
+ ///
+ [Required]
+ public ClientDto[] Items { get; set; }
+
+ public static ClientsDto FromApp(IAppEntity app, ApiController controller)
+ {
+ var result = new ClientsDto
+ {
+ Items = app.Clients.Select(x => ClientDto.FromClient(x.Key, x.Value, controller, app.Name)).ToArray()
+ };
+
+ return CreateLinks(result, controller, app.Name);
+ }
+
+ private static ClientsDto CreateLinks(ClientsDto result, ApiController controller, string app)
+ {
+ return result;
+ }
+ }
+}
diff --git a/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs b/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs
index 13570a1bf..885b4fba4 100644
--- a/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs
+++ b/src/Squidex/Areas/Api/Controllers/Users/Models/ResourcesDto.cs
@@ -7,6 +7,8 @@
using Squidex.Areas.Api.Controllers.Backups;
using Squidex.Areas.Api.Controllers.EventConsumers;
+using Squidex.Areas.Api.Controllers.Languages;
+using Squidex.Areas.Api.Controllers.Ping;
using Squidex.Shared;
using Squidex.Web;
@@ -18,6 +20,10 @@ namespace Squidex.Areas.Api.Controllers.Users.Models
{
var result = new ResourcesDto();
+ result.AddGetLink("ping", controller.Url(x => nameof(x.GetPing)));
+
+ result.AddGetLink("languages", controller.Url(x => nameof(x.GetLanguages)));
+
if (controller.HasPermission(Permissions.AdminEventsRead))
{
result.AddGetLink("admin/eventConsumers", controller.Url(x => nameof(x.GetEventConsumers)));
diff --git a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs
index 0ab2944c5..039ce367a 100644
--- a/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs
+++ b/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs
@@ -63,7 +63,7 @@ namespace Squidex.Areas.Api.Controllers.Users
/// 200 => User resources returned.
///
[HttpGet]
- [Route("user/resources/")]
+ [Route("/")]
[ProducesResponseType(typeof(ResourcesDto), 200)]
[ApiPermission]
public IActionResult GetUserResources()
diff --git a/src/Squidex/app/features/rules/pages/rules/rules-page.component.scss b/src/Squidex/app/features/rules/pages/rules/rules-page.component.scss
index e4dc92750..7d868467c 100644
--- a/src/Squidex/app/features/rules/pages/rules/rules-page.component.scss
+++ b/src/Squidex/app/features/rules/pages/rules/rules-page.component.scss
@@ -1,10 +1,6 @@
@import '_vars';
@import '_mixins';
-sqx-toggle {
- display: inline-block;
-}
-
.rule-element {
display: block;
}
diff --git a/src/Squidex/app/features/settings/pages/more/more-page.component.ts b/src/Squidex/app/features/settings/pages/more/more-page.component.ts
index c11a5011f..ce3befc0a 100644
--- a/src/Squidex/app/features/settings/pages/more/more-page.component.ts
+++ b/src/Squidex/app/features/settings/pages/more/more-page.component.ts
@@ -23,7 +23,7 @@ export class MorePageComponent {
}
public archiveApp() {
- this.appsState.delete(this.appsState.appName)
+ this.appsState.delete(this.appsState.selectedAppState!)
.subscribe(() => {
this.router.navigate(['/app']);
});
diff --git a/src/Squidex/app/shared/services/app-languages.service.spec.ts b/src/Squidex/app/shared/services/app-languages.service.spec.ts
index a2909e80e..05d613ff8 100644
--- a/src/Squidex/app/shared/services/app-languages.service.spec.ts
+++ b/src/Squidex/app/shared/services/app-languages.service.spec.ts
@@ -101,7 +101,9 @@ describe('AppLanguagesService', () => {
let languages: AppLanguagesDto;
- appLanguagesService.putLanguage('my-app', resource, dto, version).subscribe();
+ appLanguagesService.putLanguage('my-app', resource, dto, version).subscribe(result => {
+ languages = result;
+ });
const req = httpMock.expectOne('http://service/p/api/apps/my-app/languages/de');
@@ -122,7 +124,7 @@ describe('AppLanguagesService', () => {
const resource: Resource = {
_links: {
- update: { method: 'PUT', href: 'api/apps/my-app/languages/de' }
+ update: { method: 'DELETE', href: 'api/apps/my-app/languages/de' }
}
};
@@ -149,7 +151,7 @@ describe('AppLanguagesService', () => {
function languagesResponse(...codes: string[]) {
return {
items: codes.map((code, i) => ({
- code: code,
+ iso2Code: code,
englishName: code,
isMaster: i === 0,
isOptional: i % 2 === 1,
diff --git a/src/Squidex/app/shared/services/apps.service.spec.ts b/src/Squidex/app/shared/services/apps.service.spec.ts
index 93dd5f089..ef1b4f3bb 100644
--- a/src/Squidex/app/shared/services/apps.service.spec.ts
+++ b/src/Squidex/app/shared/services/apps.service.spec.ts
@@ -11,10 +11,10 @@ import { inject, TestBed } from '@angular/core/testing';
import {
AnalyticsService,
ApiUrlConfig,
- AppCreatedDto,
AppDto,
AppsService,
- DateTime
+ DateTime,
+ Resource
} from '@app/shared/internal';
describe('AppsService', () => {
@@ -50,31 +50,11 @@ describe('AppsService', () => {
expect(req.request.headers.get('If-Match')).toBeNull();
req.flush([
- {
- id: '123',
- name: 'name1',
- permissions: ['Owner'],
- created: '2016-01-01',
- lastModified: '2016-02-02',
- planName: 'Free',
- planUpgrade: 'Basic'
- },
- {
- id: '456',
- name: 'name2',
- permissions: ['Owner'],
- created: '2017-01-01',
- lastModified: '2017-02-02',
- planName: 'Basic',
- planUpgrade: 'Enterprise'
- }
+ appResponse(12),
+ appResponse(13)
]);
- expect(apps!).toEqual(
- [
- new AppDto('123', 'name1', ['Owner'], DateTime.parseISO('2016-01-01'), DateTime.parseISO('2016-02-02'), 'Free', 'Basic'),
- new AppDto('456', 'name2', ['Owner'], DateTime.parseISO('2017-01-01'), DateTime.parseISO('2017-02-02'), 'Basic', 'Enterprise')
- ]);
+ expect(apps!).toEqual([createApp(12), createApp(13)]);
}));
it('should make post request to create app',
@@ -82,7 +62,7 @@ describe('AppsService', () => {
const dto = { name: 'new-app' };
- let app: AppCreatedDto;
+ let app: AppDto;
appsService.postApp(dto).subscribe(result => {
app = result;
@@ -93,25 +73,21 @@ describe('AppsService', () => {
expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toBeNull();
- req.flush({
- id: '123',
- permissions: ['Reader'],
- planName: 'Basic',
- planUpgrade: 'Enterprise'
- });
+ req.flush(appResponse(12));
- expect(app!).toEqual({
- id: '123',
- permissions: ['Reader'],
- planName: 'Basic',
- planUpgrade: 'Enterprise'
- });
+ expect(app!).toEqual(createApp(12));
}));
it('should make delete request to archive app',
- inject([AppsService, HttpTestingController], (appsService: AppsService, httpMock: HttpTestingController) => {
+ inject([AppsService, HttpTestingController], (appsService: AppsService, httpMock: HttpTestingController) => {
- appsService.deleteApp('my-app').subscribe();
+ const resource: Resource = {
+ _links: {
+ delete: { method: 'DELETE', href: '/api/apps/my-app' }
+ }
+ };
+
+ appsService.deleteApp(resource).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app');
@@ -120,4 +96,39 @@ describe('AppsService', () => {
req.flush({});
}));
-});
\ No newline at end of file
+
+ function appResponse(id: number, suffix = '') {
+ return {
+ id: `id${id}`,
+ name: `name${id}${suffix}`,
+ permissions: ['Owner'],
+ created: `${id % 1000 + 2000}-12-12T10:10`,
+ lastModified: `${id % 1000 + 2000}-11-11T10:10`,
+ canAccessApi: id % 2 === 0,
+ canAccessContent: id % 2 === 0,
+ planName: 'Free',
+ planUpgrade: 'Basic',
+ _links: {
+ schemas: { method: 'GET', href: '/schemas' }
+ }
+ };
+ }
+});
+
+export function createApp(id: number, suffix = '') {
+ const result = new AppDto(
+ `id${id}`,
+ `name${id}${suffix}`,
+ ['Owner'],
+ DateTime.parseISO_UTC(`${id % 1000 + 2000}-12-12T10:10`),
+ DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10`),
+ id % 2 === 0,
+ id % 2 === 0,
+ 'Free',
+ 'Basic'
+ );
+
+ result._links['schemas'] = { method: 'GET', href: '/schemas' };
+
+ return result;
+}
\ No newline at end of file
diff --git a/src/Squidex/app/shared/services/apps.service.ts b/src/Squidex/app/shared/services/apps.service.ts
index e55eabdb9..44c3dda9f 100644
--- a/src/Squidex/app/shared/services/apps.service.ts
+++ b/src/Squidex/app/shared/services/apps.service.ts
@@ -15,6 +15,7 @@ import {
ApiUrlConfig,
DateTime,
pretifyError,
+ Resource,
ResourceLinks,
withLinks
} from '@app/framework';
@@ -28,6 +29,8 @@ export class AppDto {
public readonly permissions: string[],
public readonly created: DateTime,
public readonly lastModified: DateTime,
+ public readonly canAccessApi: boolean,
+ public readonly canAccessContent: boolean,
public readonly planName?: string,
public readonly planUpgrade?: string
) {
@@ -39,13 +42,6 @@ export interface CreateAppDto {
readonly template?: string;
}
-export interface AppCreatedDto {
- readonly id: string;
- readonly permissions: string[];
- readonly planName?: string;
- readonly planUpgrade?: string;
-}
-
@Injectable()
export class AppsService {
constructor(
@@ -67,22 +63,27 @@ export class AppsService {
pretifyError('Failed to load apps. Please reload.'));
}
- public postApp(dto: CreateAppDto): Observable {
+ public postApp(dto: CreateAppDto): Observable {
const url = this.apiUrl.buildUrl('api/apps');
- return this.http.post(url, dto).pipe(
+ return this.http.post(url, dto).pipe(
+ map(body => {
+ return parseApp(body);
+ }),
tap(() => {
this.analytics.trackEvent('App', 'Created', dto.name);
}),
pretifyError('Failed to create app. Please reload.'));
}
- public deleteApp(appName: string): Observable {
- const url = this.apiUrl.buildUrl(`api/apps/${appName}`);
+ public deleteApp(resource: Resource): Observable {
+ const link = resource._links['delete'];
+
+ const url = this.apiUrl.buildUrl(link.href);
- return this.http.delete(url).pipe(
+ return this.http.request(link.method, url).pipe(
tap(() => {
- this.analytics.trackEvent('App', 'Archived', appName);
+ this.analytics.trackEvent('App', 'Archived');
}),
pretifyError('Failed to archive app. Please reload.'));
}
@@ -96,6 +97,8 @@ function parseApp(response: any) {
response.permissions,
DateTime.parseISO(response.created),
DateTime.parseISO(response.lastModified),
+ response.canAccessApi,
+ response.canAccessContent,
response.planName,
response.planUpgrade),
response);
diff --git a/src/Squidex/app/shared/services/users.service.spec.ts b/src/Squidex/app/shared/services/users.service.spec.ts
index 546199ccf..9a45881f0 100644
--- a/src/Squidex/app/shared/services/users.service.spec.ts
+++ b/src/Squidex/app/shared/services/users.service.spec.ts
@@ -124,7 +124,7 @@ describe('UsersService', () => {
resources = result;
});
- const req = httpMock.expectOne('http://service/p/api/user/resources');
+ const req = httpMock.expectOne('http://service/p/api');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull();
diff --git a/src/Squidex/app/shared/services/users.service.ts b/src/Squidex/app/shared/services/users.service.ts
index 77bfe8fa1..c3cfd6a4f 100644
--- a/src/Squidex/app/shared/services/users.service.ts
+++ b/src/Squidex/app/shared/services/users.service.ts
@@ -67,7 +67,7 @@ export class UsersService {
}
public getResources(): Observable {
- const url = this.apiUrl.buildUrl(`api/user/resources`);
+ const url = this.apiUrl.buildUrl(`api`);
return this.http.get(url).pipe(
map(body => {
diff --git a/src/Squidex/app/shared/state/apps.state.spec.ts b/src/Squidex/app/shared/state/apps.state.spec.ts
index 0e19eacc7..68e1c71c8 100644
--- a/src/Squidex/app/shared/state/apps.state.spec.ts
+++ b/src/Squidex/app/shared/state/apps.state.spec.ts
@@ -12,19 +12,16 @@ import {
AppDto,
AppsService,
AppsState,
- DateTime,
DialogService
} from '@app/shared/internal';
-describe('AppsState', () => {
- const now = DateTime.now();
+import { createApp } from '../services/apps.service.spec';
- const oldApps = [
- new AppDto('id1', 'old-name1', ['Owner'], now, now),
- new AppDto('id2', 'old-name2', ['Owner'], now, now)
- ];
+describe('AppsState', () => {
+ const app1 = createApp(1);
+ const app2 = createApp(2);
- const newApp = new AppDto('id3', 'new-name', ['Owner'], now, now);
+ const newApp = createApp(3);
let dialogs: IMock;
let appsService: IMock;
@@ -36,7 +33,7 @@ describe('AppsState', () => {
appsService = Mock.ofType();
appsService.setup(x => x.getApps())
- .returns(() => of(oldApps)).verifiable();
+ .returns(() => of([app1, app2])).verifiable();
appsState = new AppsState(appsService.object, dialogs.object);
appsState.load().subscribe();
@@ -47,18 +44,18 @@ describe('AppsState', () => {
});
it('should load apps', () => {
- expect(appsState.snapshot.apps.values).toEqual(oldApps);
+ expect(appsState.snapshot.apps.values).toEqual([app1, app2]);
});
it('should select app', () => {
let selectedApp: AppDto;
- appsState.select(oldApps[0].name).subscribe(x => {
+ appsState.select(app1.name).subscribe(x => {
selectedApp = x!;
});
- expect(selectedApp!).toBe(oldApps[0]);
- expect(appsState.snapshot.selectedApp).toBe(oldApps[0]);
+ expect(selectedApp!).toBe(app1);
+ expect(appsState.snapshot.selectedApp).toBe(app1);
});
it('should return null on select when unselecting user', () => {
@@ -87,46 +84,46 @@ describe('AppsState', () => {
const request = { ...newApp };
appsService.setup(x => x.postApp(request))
- .returns(() => of({ ...newApp, permissions: ['Owner'] })).verifiable();
+ .returns(() => of(newApp)).verifiable();
- appsState.create(request, now).subscribe();
+ appsState.create(request).subscribe();
- expect(appsState.snapshot.apps.values).toEqual([newApp, ...oldApps]);
+ expect(appsState.snapshot.apps.values).toEqual([app1, app2, newApp]);
});
it('should remove app from snapshot when archived', () => {
const request = { ...newApp };
appsService.setup(x => x.postApp(request))
- .returns(() => of({ ...newApp, permissions: ['Owner'] })).verifiable();
+ .returns(() => of(newApp)).verifiable();
- appsService.setup(x => x.deleteApp(newApp.name))
+ appsService.setup(x => x.deleteApp(newApp))
.returns(() => of({})).verifiable();
- appsState.create(request, now).subscribe();
+ appsState.create(request).subscribe();
const appsAfterCreate = appsState.snapshot.apps.values;
- appsState.delete(newApp.name).subscribe();
+ appsState.delete(newApp).subscribe();
const appsAfterDelete = appsState.snapshot.apps.values;
- expect(appsAfterCreate).toEqual([newApp, ...oldApps]);
- expect(appsAfterDelete).toEqual(oldApps);
+ expect(appsAfterCreate).toEqual([app1, app2, newApp]);
+ expect(appsAfterDelete).toEqual([app1, app2]);
});
- it('should selected app from snapshot when archived', () => {
+ it('should remove selected app from snapshot when archived', () => {
const request = { ...newApp };
appsService.setup(x => x.postApp(request))
- .returns(() => of({ ...newApp, permissions: ['Owner'] })).verifiable();
+ .returns(() => of(newApp)).verifiable();
- appsService.setup(x => x.deleteApp(newApp.name))
+ appsService.setup(x => x.deleteApp(newApp))
.returns(() => of({})).verifiable();
- appsState.create(request, now).subscribe();
+ appsState.create(request).subscribe();
appsState.select(newApp.name).subscribe();
- appsState.delete(newApp.name).subscribe();
+ appsState.delete(newApp).subscribe();
expect(appsState.snapshot.selectedApp).toBeNull();
});
diff --git a/src/Squidex/app/shared/state/apps.state.ts b/src/Squidex/app/shared/state/apps.state.ts
index 32b82d34e..dc29ece97 100644
--- a/src/Squidex/app/shared/state/apps.state.ts
+++ b/src/Squidex/app/shared/state/apps.state.ts
@@ -10,7 +10,6 @@ import { Observable, of } from 'rxjs';
import { distinctUntilChanged, filter, map, tap } from 'rxjs/operators';
import {
- DateTime,
DialogService,
ImmutableArray,
shareSubscribed,
@@ -18,7 +17,6 @@ import {
} from '@app/framework';
import {
- AppCreatedDto,
AppDto,
AppsService,
CreateAppDto
@@ -42,6 +40,10 @@ export class AppsState extends State {
return this.snapshot.selectedApp ? this.snapshot.selectedApp.name : '';
}
+ public get selectedAppState() {
+ return this.snapshot.selectedApp;
+ }
+
public selectedApp =
this.changes.pipe(map(s => s.selectedApp),
distinctUntilChanged(sameApp));
@@ -85,9 +87,8 @@ export class AppsState extends State {
shareSubscribed(this.dialogs));
}
- public create(request: CreateAppDto, now?: DateTime): Observable {
+ public create(request: CreateAppDto): Observable {
return this.appsService.postApp(request).pipe(
- map(payload => createApp(request, payload, now)),
tap(created => {
this.next(s => {
const apps = s.apps.push(created).sortByStringAsc(x => x.name);
@@ -98,32 +99,21 @@ export class AppsState extends State {
shareSubscribed(this.dialogs, { silent: true }));
}
- public delete(name: string): Observable {
- return this.appsService.deleteApp(name).pipe(
+ public delete(app: AppDto): Observable {
+ return this.appsService.deleteApp(app).pipe(
tap(() => {
this.next(s => {
- const apps = s.apps.filter(x => x.name !== name);
+ const apps = s.apps.filter(x => x.name !== app.name);
- const selectedApp = s.selectedApp && s.selectedApp.name === name ? null : s.selectedApp;
+ const selectedApp =
+ s.selectedApp &&
+ s.selectedApp.name === app.name ?
+ null :
+ s.selectedApp;
return { ...s, apps, selectedApp };
});
}),
shareSubscribed(this.dialogs));
}
-}
-
-function createApp(request: CreateAppDto, response: AppCreatedDto, now?: DateTime) {
- now = now || DateTime.now();
-
- const app = new AppDto(
- response.id,
- request.name,
- response.permissions,
- now,
- now,
- response.planName,
- response.planUpgrade);
-
- return app;
}
\ No newline at end of file
diff --git a/src/Squidex/app/shared/state/languages.state.spec.ts b/src/Squidex/app/shared/state/languages.state.spec.ts
index 92fc6aa7e..04769a297 100644
--- a/src/Squidex/app/shared/state/languages.state.spec.ts
+++ b/src/Squidex/app/shared/state/languages.state.spec.ts
@@ -9,6 +9,7 @@ import { of } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq';
import {
+ AppLanguagesPayload,
AppLanguagesService,
DialogService,
ImmutableArray,
@@ -71,11 +72,11 @@ describe('LanguagesState', () => {
expect(languagesState.snapshot.languages.values).toEqual([
{
language: oldLanguages.items[0],
- fallbackLanguages: ImmutableArray.empty(),
- fallbackLanguagesNew: ImmutableArray.of([oldLanguages[1]])
+ fallbackLanguages: ImmutableArray.of([oldLanguages.items[1]]),
+ fallbackLanguagesNew: ImmutableArray.empty()
}, {
language: oldLanguages.items[1],
- fallbackLanguages: ImmutableArray.of([oldLanguages[0]]),
+ fallbackLanguages: ImmutableArray.of([oldLanguages.items[0]]),
fallbackLanguagesNew: ImmutableArray.empty()
}
]);
@@ -108,15 +109,7 @@ describe('LanguagesState', () => {
languagesState.add(languageIT).subscribe();
- expect(languagesState.snapshot.languages.values).toEqual([
- {
- language: oldLanguages[0],
- fallbackLanguages: ImmutableArray.empty(),
- fallbackLanguagesNew: ImmutableArray.empty()
- }
- ]);
- expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageDE, languageIT, languageES]);
- expect(languagesState.snapshot.version).toEqual(newVersion);
+ expectUpdated(updated);
});
it('should update language in snapshot when updated', () => {
@@ -124,39 +117,35 @@ describe('LanguagesState', () => {
const request = { isMaster: true, isOptional: false, fallback: [] };
- languagesService.setup(x => x.putLanguage(app, oldLanguages[1].iso2Code, request, version))
+ languagesService.setup(x => x.putLanguage(app, oldLanguages.items[1], request, version))
.returns(() => of(versioned(newVersion, updated))).verifiable();
- languagesState.update(oldLanguages[1], request).subscribe();
+ languagesState.update(oldLanguages.items[1], request).subscribe();
- expect(languagesState.snapshot.languages.values).toEqual([
- {
- language: oldLanguages[0],
- fallbackLanguages: ImmutableArray.empty(),
- fallbackLanguagesNew: ImmutableArray.empty()
- }
- ]);
- expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageDE, languageIT, languageES]);
- expect(languagesState.snapshot.version).toEqual(newVersion);
+ expectUpdated(updated);
});
it('should remove language from snapshot when deleted', () => {
const updated = createLanguages('de');
- languagesService.setup(x => x.deleteLanguage(app, oldLanguages[1].iso2Code, version))
+ languagesService.setup(x => x.deleteLanguage(app, oldLanguages.items[1], version))
.returns(() => of(versioned(newVersion, updated))).verifiable();
- languagesState.remove(oldLanguages[1]).subscribe();
+ languagesState.remove(oldLanguages.items[1]).subscribe();
+
+ expectUpdated(updated);
+ });
+ function expectUpdated(updated: AppLanguagesPayload) {
expect(languagesState.snapshot.languages.values).toEqual([
{
- language: oldLanguages[0],
+ language: updated.items[0],
fallbackLanguages: ImmutableArray.empty(),
fallbackLanguagesNew: ImmutableArray.empty()
}
]);
- expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageDE, languageIT, languageES]);
+ expect(languagesState.snapshot.allLanguagesNew.values).toEqual([languageEN, languageIT, languageES]);
expect(languagesState.snapshot.version).toEqual(newVersion);
- });
+ }
});
});
\ No newline at end of file
diff --git a/src/Squidex/app/shared/state/languages.state.ts b/src/Squidex/app/shared/state/languages.state.ts
index c7ba25e19..00ff0806b 100644
--- a/src/Squidex/app/shared/state/languages.state.ts
+++ b/src/Squidex/app/shared/state/languages.state.ts
@@ -182,7 +182,7 @@ export class LanguagesState extends State {
ImmutableArray.of(
language.fallback
.map(l => languages.find(x => x.iso2Code === l)).filter(x => !!x)
- .map(x => x)),
+ .map(l => l!)),
fallbackLanguagesNew:
languages
.filter(l =>
diff --git a/src/Squidex/app/shell/pages/app/left-menu.component.html b/src/Squidex/app/shell/pages/app/left-menu.component.html
index 207dd51ee..1d12a0eb3 100644
--- a/src/Squidex/app/shell/pages/app/left-menu.component.html
+++ b/src/Squidex/app/shell/pages/app/left-menu.component.html
@@ -4,7 +4,7 @@
Schemas
-
+
Content
@@ -24,7 +24,7 @@
Settings
-
+
API