Browse Source

CreateLinks refactored.

pull/363/head
Sebastian 7 years ago
parent
commit
0dbd95b91f
  1. 1
      src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs
  2. 39
      src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs
  3. 1
      src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
  4. 58
      src/Squidex/Areas/Api/Controllers/Apps/Models/AppDto.cs
  5. 14
      src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs
  6. 11
      src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs
  7. 6
      src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs
  8. 6
      src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs
  9. 13
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs
  10. 10
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs
  11. 12
      src/Squidex/Areas/Api/Controllers/Apps/Models/RoleDto.cs
  12. 21
      src/Squidex/Areas/Api/Controllers/Apps/Models/RolesDto.cs
  13. 10
      src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobDto.cs
  14. 10
      src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobsDto.cs
  15. 18
      src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs
  16. 8
      src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs
  17. 18
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs
  18. 12
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs
  19. 8
      src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs
  20. 12
      src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs
  21. 26
      src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs
  22. 10
      src/Squidex/Areas/Api/Controllers/Users/Models/UsersDto.cs
  23. 4
      src/Squidex/app/features/administration/services/users.service.ts
  24. 8
      src/Squidex/app/features/settings/pages/clients/client.component.html
  25. 22
      src/Squidex/app/features/settings/pages/clients/client.component.ts
  26. 2
      src/Squidex/app/features/settings/pages/clients/clients-page.component.html
  27. 12
      src/Squidex/app/framework/angular/http/http-extensions.ts
  28. 4
      src/Squidex/app/shared/services/app-languages.service.ts
  29. 2
      src/Squidex/app/shared/services/apps.service.ts
  30. 6
      src/Squidex/app/shared/services/assets.service.ts
  31. 2
      src/Squidex/app/shared/services/backups.service.ts
  32. 123
      src/Squidex/app/shared/services/clients.service.spec.ts
  33. 80
      src/Squidex/app/shared/services/clients.service.ts
  34. 8
      src/Squidex/app/shared/services/contents.service.ts
  35. 2
      src/Squidex/app/shared/services/contributors.service.spec.ts
  36. 10
      src/Squidex/app/shared/services/contributors.service.ts
  37. 4
      src/Squidex/app/shared/services/patterns.service.ts
  38. 4
      src/Squidex/app/shared/services/plans.service.ts
  39. 4
      src/Squidex/app/shared/services/roles.service.ts
  40. 8
      src/Squidex/app/shared/services/rules.service.ts
  41. 8
      src/Squidex/app/shared/services/schemas.service.ts
  42. 46
      src/Squidex/app/shared/state/clients.state.spec.ts
  43. 65
      src/Squidex/app/shared/state/clients.state.ts
  44. 10
      src/Squidex/app/shared/state/contributors.state.spec.ts
  45. 4
      src/Squidex/app/shared/state/contributors.state.ts
  46. 6
      src/Squidex/app/shared/state/languages.state.spec.ts

1
src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;

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

@ -47,7 +47,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
[ApiCosts(0)]
public IActionResult GetRoles(string app)
{
var response = RolesDto.FromApp(App);
var response = RolesDto.FromApp(App, this);
Response.Headers[HeaderNames.ETag] = App.Version.ToString();
@ -82,20 +82,23 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <param name="app">The name of the app.</param>
/// <param name="request">Role object that needs to be added to the app.</param>
/// <returns>
/// 200 => User assigned to app.
/// 201 => User assigned to app.
/// 400 => Role name already in use.
/// 404 => App not found.
/// </returns>
[HttpPost]
[Route("apps/{app}/roles/")]
[ProducesResponseType(typeof(RolesDto), 200)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppRolesCreate)]
[ApiCosts(1)]
public async Task<IActionResult> PostRole(string app, [FromBody] AddRoleDto request)
{
await CommandBus.PublishAsync(request.ToCommand());
var command = request.ToCommand();
var response = await InvokeCommandAsync(command);
return NoContent();
return CreatedAtAction(nameof(GetRoles), new { app }, response);
}
/// <summary>
@ -105,19 +108,22 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <param name="role">The name of the role to be updated.</param>
/// <param name="request">Role to be updated for the app.</param>
/// <returns>
/// 204 => Role updated.
/// 200 => Role updated.
/// 400 => Role request not valid.
/// 404 => Role or app not found.
/// </returns>
[HttpPut]
[Route("apps/{app}/roles/{role}/")]
[ProducesResponseType(typeof(RolesDto), 200)]
[ApiPermission(Permissions.AppRolesUpdate)]
[ApiCosts(1)]
public async Task<IActionResult> UpdateRole(string app, string role, [FromBody] UpdateRoleDto request)
{
await CommandBus.PublishAsync(request.ToCommand(role));
var command = request.ToCommand(role);
var response = await InvokeCommandAsync(command);
return NoContent();
return Ok(response);
}
/// <summary>
@ -126,20 +132,33 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// <param name="app">The name of the app.</param>
/// <param name="role">The name of the role.</param>
/// <returns>
/// 204 => Role deleted.
/// 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}/")]
[ProducesResponseType(typeof(RolesDto), 200)]
[ProducesResponseType(typeof(ErrorDto), 400)]
[ApiPermission(Permissions.AppRolesDelete)]
[ApiCosts(1)]
public async Task<IActionResult> DeleteRole(string app, string role)
{
await CommandBus.PublishAsync(new DeleteRole { Name = role });
var command = new DeleteRole { Name = role };
var response = await InvokeCommandAsync(command);
return Ok(response);
}
private async Task<RolesDto> InvokeCommandAsync(ICommand command)
{
var context = await CommandBus.PublishAsync(command);
var result = context.Result<IAppEntity>();
var response = RolesDto.FromApp(result, this);
return NoContent();
return response;
}
}
}

1
src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;

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

@ -98,7 +98,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
result.PlanUpgrade = plans.GetPlanUpgradeForApp(app)?.Name;
}
return CreateLinks(result, controller, permissions);
return result.CreateLinks(controller, permissions);
}
private static PermissionSet GetPermissions(IAppEntity app, string userId, PermissionSet userPermissions)
@ -118,73 +118,73 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
return new PermissionSet(permissions);
}
private static AppDto CreateLinks(AppDto result, ApiController controller, PermissionSet permissions)
private AppDto CreateLinks(ApiController controller, PermissionSet permissions)
{
var values = new { app = result.Name };
var values = new { app = Name };
result.AddGetLink("ping", controller.Url<PingController>(x => nameof(x.GetAppPing), values));
AddGetLink("ping", controller.Url<PingController>(x => nameof(x.GetAppPing), values));
if (controller.HasPermission(AllPermissions.AppDelete, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppDelete, Name, permissions: permissions))
{
result.AddDeleteLink("delete", controller.Url<AppsController>(x => nameof(x.DeleteApp), values));
AddDeleteLink("delete", controller.Url<AppsController>(x => nameof(x.DeleteApp), values));
}
if (controller.HasPermission(AllPermissions.AppAssetsRead, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppAssetsRead, Name, permissions: permissions))
{
result.AddGetLink("assets", controller.Url<AssetsController>(x => nameof(x.GetAssets), values));
AddGetLink("assets", controller.Url<AssetsController>(x => nameof(x.GetAssets), values));
}
if (controller.HasPermission(AllPermissions.AppBackupsRead, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppBackupsRead, Name, permissions: permissions))
{
result.AddGetLink("backups", controller.Url<BackupsController>(x => nameof(x.GetBackups), values));
AddGetLink("backups", controller.Url<BackupsController>(x => nameof(x.GetBackups), values));
}
if (controller.HasPermission(AllPermissions.AppClientsRead, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppClientsRead, Name, permissions: permissions))
{
result.AddGetLink("clients", controller.Url<AppClientsController>(x => nameof(x.GetClients), values));
AddGetLink("clients", controller.Url<AppClientsController>(x => nameof(x.GetClients), values));
}
if (controller.HasPermission(AllPermissions.AppContributorsRead, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppContributorsRead, Name, permissions: permissions))
{
result.AddGetLink("contributors", controller.Url<AppContributorsController>(x => nameof(x.GetContributors), values));
AddGetLink("contributors", controller.Url<AppContributorsController>(x => nameof(x.GetContributors), values));
}
if (controller.HasPermission(AllPermissions.AppCommon, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppCommon, Name, permissions: permissions))
{
result.AddGetLink("languages", controller.Url<AppLanguagesController>(x => nameof(x.GetLanguages), values));
AddGetLink("languages", controller.Url<AppLanguagesController>(x => nameof(x.GetLanguages), values));
}
if (controller.HasPermission(AllPermissions.AppCommon, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppCommon, Name, permissions: permissions))
{
result.AddGetLink("patterns", controller.Url<AppPatternsController>(x => nameof(x.GetPatterns), values));
AddGetLink("patterns", controller.Url<AppPatternsController>(x => nameof(x.GetPatterns), values));
}
if (controller.HasPermission(AllPermissions.AppPlansRead, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppPlansRead, Name, permissions: permissions))
{
result.AddGetLink("plans", controller.Url<AppPlansController>(x => nameof(x.GetPlans), values));
AddGetLink("plans", controller.Url<AppPlansController>(x => nameof(x.GetPlans), values));
}
if (controller.HasPermission(AllPermissions.AppRolesRead, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppRolesRead, Name, permissions: permissions))
{
result.AddGetLink("roles", controller.Url<AppRolesController>(x => nameof(x.GetRoles), values));
AddGetLink("roles", controller.Url<AppRolesController>(x => nameof(x.GetRoles), values));
}
if (controller.HasPermission(AllPermissions.AppRulesRead, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppRulesRead, Name, permissions: permissions))
{
result.AddGetLink("rules", controller.Url<RulesController>(x => nameof(x.GetRules), values));
AddGetLink("rules", controller.Url<RulesController>(x => nameof(x.GetRules), values));
}
if (controller.HasPermission(AllPermissions.AppCommon, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppCommon, Name, permissions: permissions))
{
result.AddGetLink("schemas", controller.Url<SchemasController>(x => nameof(x.GetSchemas), values));
AddGetLink("schemas", controller.Url<SchemasController>(x => nameof(x.GetSchemas), values));
}
if (controller.HasPermission(AllPermissions.AppSchemasCreate, result.Name, permissions: permissions))
if (controller.HasPermission(AllPermissions.AppSchemasCreate, Name, permissions: permissions))
{
result.AddPostLink("schemas/create", controller.Url<SchemasController>(x => nameof(x.PostSchema), values));
AddPostLink("schemas/create", controller.Url<SchemasController>(x => nameof(x.PostSchema), values));
}
return result;
return this;
}
}
}

14
src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs

@ -56,27 +56,27 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
Fallback = language.LanguageFallbacks.ToArray()
});
return CreateLinks(result, controller, app);
return result.CreateLinks(controller, app);
}
private static AppLanguageDto CreateLinks(AppLanguageDto result, ApiController controller, IAppEntity app)
private AppLanguageDto CreateLinks(ApiController controller, IAppEntity app)
{
var values = new { app = app.Name, language = result.Iso2Code };
var values = new { app = app.Name, language = Iso2Code };
if (!result.IsMaster)
if (!IsMaster)
{
if (controller.HasPermission(Permissions.AppLanguagesUpdate, app.Name))
{
result.AddPutLink("update", controller.Url<AppLanguagesController>(x => nameof(x.PutLanguage), values));
AddPutLink("update", controller.Url<AppLanguagesController>(x => nameof(x.PutLanguage), values));
}
if (controller.HasPermission(Permissions.AppLanguagesDelete, app.Name) && app.LanguagesConfig.Count > 1)
{
result.AddDeleteLink("delete", controller.Url<AppLanguagesController>(x => nameof(x.DeleteLanguage), values));
AddDeleteLink("delete", controller.Url<AppLanguagesController>(x => nameof(x.DeleteLanguage), values));
}
}
return result;
return this;
}
}
}

11
src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Squidex.Domain.Apps.Core.Apps;
@ -34,21 +33,21 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
.ToArray()
};
return CreateLinks(result, controller, app.Name);
return result.CreateLinks(controller, app.Name);
}
private static AppLanguagesDto CreateLinks(AppLanguagesDto result, ApiController controller, string app)
private AppLanguagesDto CreateLinks(ApiController controller, string app)
{
var values = new { app };
result.AddSelfLink(controller.Url<AppLanguagesController>(x => nameof(x.GetLanguages), values));
AddSelfLink(controller.Url<AppLanguagesController>(x => nameof(x.GetLanguages), values));
if (controller.HasPermission(Permissions.AppLanguagesCreate, app))
{
result.AddPostLink("create", controller.Url<AppLanguagesController>(x => nameof(x.PostLanguage), values));
AddPostLink("create", controller.Url<AppLanguagesController>(x => nameof(x.PostLanguage), values));
}
return result;
return this;
}
}
}

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

@ -41,12 +41,12 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
{
var result = SimpleMapper.Map(client, new ClientDto { Id = id });
return CreateLinks(result, controller, app);
return result.CreateLinks(controller, app);
}
private static ClientDto CreateLinks(ClientDto result, ApiController controller, string app)
private ClientDto CreateLinks(ApiController controller, string app)
{
return result;
return this;
}
}
}

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

@ -27,12 +27,12 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
Items = app.Clients.Select(x => ClientDto.FromClient(x.Key, x.Value, controller, app.Name)).ToArray()
};
return CreateLinks(result, controller, app.Name);
return result.CreateLinks(controller, app.Name);
}
private static ClientsDto CreateLinks(ClientsDto result, ApiController controller, string app)
private ClientsDto CreateLinks(ApiController controller, string app)
{
return result;
return this;
}
}
}

13
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.ComponentModel.DataAnnotations;
using Squidex.Shared;
using Squidex.Web;
@ -29,25 +28,25 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
{
var result = new ContributorDto { ContributorId = id, Role = role };
return CreateLinks(result, controller, app);
return result.CreateLinks(controller, app);
}
private static ContributorDto CreateLinks(ContributorDto result, ApiController controller, string app)
private ContributorDto CreateLinks(ApiController controller, string app)
{
if (!controller.IsUser(result.ContributorId))
if (!controller.IsUser(ContributorId))
{
if (controller.HasPermission(Permissions.AppContributorsAssign, app))
{
result.AddPostLink("update", controller.Url<AppContributorsController>(x => nameof(x.PostContributor), new { app }));
AddPostLink("update", controller.Url<AppContributorsController>(x => nameof(x.PostContributor), new { app }));
}
if (controller.HasPermission(Permissions.AppContributorsRevoke, app))
{
result.AddDeleteLink("delete", controller.Url<AppContributorsController>(x => nameof(x.DeleteContributor), new { app, id = result.ContributorId }));
AddDeleteLink("delete", controller.Url<AppContributorsController>(x => nameof(x.DeleteContributor), new { app, id = ContributorId }));
}
}
return result;
return this;
}
}
}

10
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs

@ -53,21 +53,21 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
result.MaxContributors = plans.GetPlanForApp(app).MaxContributors;
return CreateLinks(result, controller, app.Name);
return result.CreateLinks(controller, app.Name);
}
private static ContributorsDto CreateLinks(ContributorsDto result, ApiController controller, string app)
private ContributorsDto CreateLinks(ApiController controller, string app)
{
var values = new { app };
result.AddSelfLink(controller.Url<AppContributorsController>(x => nameof(x.GetContributors), values));
AddSelfLink(controller.Url<AppContributorsController>(x => nameof(x.GetContributors), values));
if (controller.HasPermission(Permissions.AppContributorsAssign, app))
{
result.AddPostLink("create", controller.Url<AppContributorsController>(x => nameof(x.PostContributor), values));
AddPostLink("create", controller.Url<AppContributorsController>(x => nameof(x.PostContributor), values));
}
return result;
return this;
}
}
}

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

@ -11,6 +11,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Apps.Models
{
@ -38,17 +39,24 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models
[Required]
public IEnumerable<string> Permissions { get; set; }
public static RoleDto FromRole(Role role, IAppEntity app)
public static RoleDto FromRole(Role role, IAppEntity app, ApiController controller)
{
var permissions = role.Permissions.WithoutApp(app.Name);
return new RoleDto
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()
};
return result.CreateLinks(controller, app.Name);
}
private RoleDto CreateLinks(ApiController controller, string name)
{
return this;
}
}
}

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

@ -8,22 +8,31 @@
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 RolesDto
public sealed class RolesDto : Resource
{
/// <summary>
/// The app roles.
/// The roles.
/// </summary>
[Required]
public RoleDto[] Roles { get; set; }
public RoleDto[] Items { get; set; }
public static RolesDto FromApp(IAppEntity app)
public static RolesDto FromApp(IAppEntity app, ApiController controller)
{
var roles = app.Roles.Values.Select(x => RoleDto.FromRole(x, app)).ToArray();
var result = new RolesDto
{
Items = app.Roles.Values.Select(x => RoleDto.FromRole(x, app, controller)).ToArray()
};
return new RolesDto { Roles = roles };
return result.CreateLinks(controller, app.Name);
}
private RolesDto CreateLinks(ApiController controller, string app)
{
return this;
}
}
}

10
src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobDto.cs

@ -50,19 +50,19 @@ namespace Squidex.Areas.Api.Controllers.Backups.Models
{
var result = SimpleMapper.Map(backup, new BackupJobDto());
return CreateLinks(result, controller, app);
return result.CreateLinks(controller, app);
}
private static BackupJobDto CreateLinks(BackupJobDto result, ApiController controller, string app)
private BackupJobDto CreateLinks(ApiController controller, string app)
{
var values = new { app, id = result.Id };
var values = new { app, id = Id };
if (controller.HasPermission(Permissions.AppBackupsDelete, app))
{
result.AddDeleteLink("delete", controller.Url<BackupsController>(x => nameof(x.DeleteBackup), values));
AddDeleteLink("delete", controller.Url<BackupsController>(x => nameof(x.DeleteBackup), values));
}
return result;
return this;
}
}
}

10
src/Squidex/Areas/Api/Controllers/Backups/Models/BackupJobsDto.cs

@ -29,21 +29,21 @@ namespace Squidex.Areas.Api.Controllers.Backups.Models
Items = backups.Select(x => BackupJobDto.FromBackup(x, controller, app)).ToArray()
};
return CreateLinks(result, controller, app);
return result.CreateLinks(controller, app);
}
private static BackupJobsDto CreateLinks(BackupJobsDto result, ApiController controller, string app)
private BackupJobsDto CreateLinks(ApiController controller, string app)
{
var values = new { app };
result.AddSelfLink(controller.Url<BackupsController>(x => nameof(x.GetBackups), values));
AddSelfLink(controller.Url<BackupsController>(x => nameof(x.GetBackups), values));
if (controller.HasPermission(Permissions.AppBackupsCreate, app))
{
result.AddPostLink("create", controller.Url<BackupsController>(x => nameof(x.PostBackup), values));
AddPostLink("create", controller.Url<BackupsController>(x => nameof(x.PostBackup), values));
}
return result;
return this;
}
}
}

18
src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs

@ -31,31 +31,31 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers.Models
{
var result = SimpleMapper.Map(eventConsumerInfo, new EventConsumerDto());
return CreateLinks(result, controller);
return result.CreateLinks(controller);
}
private static EventConsumerDto CreateLinks(EventConsumerDto result, ApiController controller)
private EventConsumerDto CreateLinks(ApiController controller)
{
if (controller.HasPermission(EventsManagePermission))
{
var values = new { name = result.Name };
var values = new { name = Name };
if (!result.IsResetting)
if (!IsResetting)
{
result.AddPutLink("reset", controller.Url<EventConsumersController>(x => nameof(x.ResetEventConsumer), values));
AddPutLink("reset", controller.Url<EventConsumersController>(x => nameof(x.ResetEventConsumer), values));
}
if (result.IsStopped)
if (IsStopped)
{
result.AddPutLink("start", controller.Url<EventConsumersController>(x => nameof(x.StartEventConsumer), values));
AddPutLink("start", controller.Url<EventConsumersController>(x => nameof(x.StartEventConsumer), values));
}
else
{
result.AddPutLink("stop", controller.Url<EventConsumersController>(x => nameof(x.StopEventConsumer), values));
AddPutLink("stop", controller.Url<EventConsumersController>(x => nameof(x.StopEventConsumer), values));
}
}
return result;
return this;
}
}
}

8
src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs

@ -26,14 +26,14 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers.Models
Items = items.Select(x => EventConsumerDto.FromEventConsumerInfo(x, controller)).ToArray()
};
return CreateLinks(result, controller);
return result.CreateLinks(controller);
}
private static EventConsumersDto CreateLinks(EventConsumersDto result, ApiController controller)
private EventConsumersDto CreateLinks(ApiController controller)
{
result.AddSelfLink(controller.Url<EventConsumersController>(c => nameof(c.GetEventConsumers)));
AddSelfLink(controller.Url<EventConsumersController>(c => nameof(c.GetEventConsumers)));
return result;
return this;
}
}
}

18
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleDto.cs

@ -83,36 +83,36 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
result.Trigger = RuleTriggerDtoFactory.Create(rule.RuleDef.Trigger);
}
return CreateLinks(result, controller, app);
return result.CreateLinks(controller, app);
}
private static RuleDto CreateLinks(RuleDto result, ApiController controller, string app)
private RuleDto CreateLinks(ApiController controller, string app)
{
var values = new { app, id = result.Id };
var values = new { app, id = Id };
if (controller.HasPermission(Permissions.AppRulesDisable, app))
{
if (result.IsEnabled)
if (IsEnabled)
{
result.AddPutLink("disable", controller.Url<RulesController>(x => nameof(x.DisableRule), values));
AddPutLink("disable", controller.Url<RulesController>(x => nameof(x.DisableRule), values));
}
else
{
result.AddPutLink("enable", controller.Url<RulesController>(x => nameof(x.EnableRule), values));
AddPutLink("enable", controller.Url<RulesController>(x => nameof(x.EnableRule), values));
}
}
if (controller.HasPermission(Permissions.AppRulesUpdate))
{
result.AddPutLink("update", controller.Url<RulesController>(x => nameof(x.PutRule), values));
AddPutLink("update", controller.Url<RulesController>(x => nameof(x.PutRule), values));
}
if (controller.HasPermission(Permissions.AppRulesDelete))
{
result.AddPutLink("delete", controller.Url<RulesController>(x => nameof(x.DeleteRule), values));
AddPutLink("delete", controller.Url<RulesController>(x => nameof(x.DeleteRule), values));
}
return result;
return this;
}
}
}

12
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventDto.cs

@ -71,18 +71,18 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
SimpleMapper.Map(ruleEvent, result);
SimpleMapper.Map(ruleEvent.Job, result);
return CreateLinks(result, controller, app);
return result.CreateLinks(controller, app);
}
private static RuleEventDto CreateLinks(RuleEventDto result, ApiController controller, string app)
private RuleEventDto CreateLinks(ApiController controller, string app)
{
var values = new { app, id = result.Id };
var values = new { app, id = Id };
result.AddPutLink("update", controller.Url<RulesController>(x => nameof(x.PutEvent), values));
AddPutLink("update", controller.Url<RulesController>(x => nameof(x.PutEvent), values));
result.AddDeleteLink("delete", controller.Url<RulesController>(x => nameof(x.DeleteEvent), values));
AddDeleteLink("delete", controller.Url<RulesController>(x => nameof(x.DeleteEvent), values));
return result;
return this;
}
}
}

8
src/Squidex/Areas/Api/Controllers/Rules/Models/RuleEventsDto.cs

@ -34,14 +34,14 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
Items = items.Select(x => RuleEventDto.FromRuleEvent(x, controller, app)).ToArray()
};
return CreateLinks(result, controller, app);
return result.CreateLinks(controller, app);
}
private static RuleEventsDto CreateLinks(RuleEventsDto result, ApiController controller, string app)
private RuleEventsDto CreateLinks(ApiController controller, string app)
{
result.AddSelfLink(controller.Url<RulesController>(x => nameof(x.GetEvents), new { app }));
AddSelfLink(controller.Url<RulesController>(x => nameof(x.GetEvents), new { app }));
return result;
return this;
}
}
}

12
src/Squidex/Areas/Api/Controllers/Rules/Models/RulesDto.cs

@ -34,26 +34,26 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models
Items = items.Select(x => RuleDto.FromRule(x, controller, app)).ToArray()
};
return CreateLinks(result, controller, app);
return result.CreateLinks(controller, app);
}
private static RulesDto CreateLinks(RulesDto result, ApiController controller, string app)
private RulesDto CreateLinks(ApiController controller, string app)
{
var values = new { app };
result.AddSelfLink(controller.Url<RulesController>(x => nameof(x.GetRules), values));
AddSelfLink(controller.Url<RulesController>(x => nameof(x.GetRules), values));
if (controller.HasPermission(Permissions.AppRulesCreate, app))
{
result.AddPostLink("create", controller.Url<RulesController>(x => nameof(x.PostRule), values));
AddPostLink("create", controller.Url<RulesController>(x => nameof(x.PostRule), values));
}
if (controller.HasPermission(Permissions.AppRulesEvents, app))
{
result.AddGetLink("events", controller.Url<RulesController>(x => nameof(x.GetEvents), values));
AddGetLink("events", controller.Url<RulesController>(x => nameof(x.GetEvents), values));
}
return result;
return this;
}
}
}

26
src/Squidex/Areas/Api/Controllers/Users/Models/UserDto.cs

@ -57,43 +57,43 @@ namespace Squidex.Areas.Api.Controllers.Users.Models
var result = SimpleMapper.Map(user, new UserDto { DisplayName = userName, Permissions = userPermssions });
return CreateLinks(result, controller);
return result.CreateLinks(controller);
}
private static UserDto CreateLinks(UserDto result, ApiController controller)
private UserDto CreateLinks(ApiController controller)
{
var values = new { id = result.Id };
var values = new { id = Id };
if (controller is UserManagementController)
{
result.AddSelfLink(controller.Url<UserManagementController>(c => nameof(c.GetUser), values));
AddSelfLink(controller.Url<UserManagementController>(c => nameof(c.GetUser), values));
}
else
{
result.AddSelfLink(controller.Url<UsersController>(c => nameof(c.GetUser), values));
AddSelfLink(controller.Url<UsersController>(c => nameof(c.GetUser), values));
}
if (!controller.IsUser(result.Id))
if (!controller.IsUser(Id))
{
if (controller.HasPermission(LockPermission) && !result.IsLocked)
if (controller.HasPermission(LockPermission) && !IsLocked)
{
result.AddPutLink("lock", controller.Url<UserManagementController>(c => nameof(c.LockUser), values));
AddPutLink("lock", controller.Url<UserManagementController>(c => nameof(c.LockUser), values));
}
if (controller.HasPermission(UnlockPermission) && result.IsLocked)
if (controller.HasPermission(UnlockPermission) && IsLocked)
{
result.AddPutLink("unlock", controller.Url<UserManagementController>(c => nameof(c.UnlockUser), values));
AddPutLink("unlock", controller.Url<UserManagementController>(c => nameof(c.UnlockUser), values));
}
}
if (controller.HasPermission(UpdatePermission))
{
result.AddPutLink("update", controller.Url<UserManagementController>(c => nameof(c.PutUser), values));
AddPutLink("update", controller.Url<UserManagementController>(c => nameof(c.PutUser), values));
}
result.AddGetLink("picture", controller.Url<UsersController>(c => nameof(c.GetUserPicture), values));
AddGetLink("picture", controller.Url<UsersController>(c => nameof(c.GetUserPicture), values));
return result;
return this;
}
}
}

10
src/Squidex/Areas/Api/Controllers/Users/Models/UsersDto.cs

@ -36,19 +36,19 @@ namespace Squidex.Areas.Api.Controllers.Users.Models
Items = items.Select(x => UserDto.FromUser(x, controller)).ToArray()
};
return CreateLinks(result, controller);
return result.CreateLinks(controller);
}
private static UsersDto CreateLinks(UsersDto result, ApiController controller)
private UsersDto CreateLinks(ApiController controller)
{
result.AddSelfLink(controller.Url<UserManagementController>(c => nameof(c.GetUsers)));
AddSelfLink(controller.Url<UserManagementController>(c => nameof(c.GetUsers)));
if (controller.HasPermission(CreatePermissions))
{
result.AddPostLink("create", controller.Url<UserManagementController>(c => nameof(c.PostUser)));
AddPostLink("create", controller.Url<UserManagementController>(c => nameof(c.PostUser)));
}
return result;
return this;
}
}
}

4
src/Squidex/app/features/administration/services/users.service.ts

@ -73,7 +73,7 @@ export class UsersService {
public getUser(id: string): Observable<UserDto> {
const url = this.apiUrl.buildUrl(`api/user-management/${id}`);
return this.http.get<any>(url).pipe(
return this.http.get(url).pipe(
map(body => {
return parseUser(body);
}),
@ -83,7 +83,7 @@ export class UsersService {
public postUser(dto: CreateUserDto): Observable<UserDto> {
const url = this.apiUrl.buildUrl('api/user-management');
return this.http.post<any>(url, dto).pipe(
return this.http.post(url, dto).pipe(
map(body => {
return parseUser(body);
}),

8
src/Squidex/app/features/settings/pages/clients/client.component.html

@ -21,7 +21,7 @@
{{client.name}}
</h3>
<i class="client-edit icon-pencil" (click)="toggleRename()"></i>
<i class="client-edit icon-pencil" *ngIf="isEditable" (click)="toggleRename()"></i>
</ng-template>
</div>
<div class="col-auto">
@ -29,6 +29,7 @@
</div>
<div class="col-auto cell-actions no-padding">
<button type="button" class="btn btn-text-danger"
[disabled]="client | sqxHasNoLink:'delete'"
(sqxConfirmClick)="revoke()"
confirmTitle="Revoke client"
confirmText="Do you really want to revoke the client?">
@ -70,7 +71,10 @@
Role
</label>
<div class="col cell-input">
<select class="form-control" [ngModel]="client.role" (ngModelChange)="update($event)">
<select class="form-control"
[disabled]="!isEditable"
[ngModel]="client.role"
(ngModelChange)="update($event)">
<option *ngFor="let role of clientRoles; trackBy: trackByRole" [ngValue]="role.name">{{role.name}}</option>
</select>
</div>

22
src/Squidex/app/features/settings/pages/clients/client.component.ts

@ -17,6 +17,7 @@ import {
ClientsState,
DialogModel,
DialogService,
hasAnyLink,
RenameClientForm,
RoleDto
} from '@app/shared';
@ -36,6 +37,7 @@ export class ClientComponent implements OnChanges {
public clientRoles: RoleDto[];
public isRenaming = false;
public isEditable = false;
public connectToken: AccessTokenDto;
public connectDialog = new DialogModel();
@ -59,6 +61,8 @@ export class ClientComponent implements OnChanges {
}
public ngOnChanges() {
this.isEditable = hasAnyLink(this.client, 'update');
this.renameForm.load(this.client);
const app = this.appsState.appName;
@ -79,16 +83,18 @@ export class ClientComponent implements OnChanges {
}
public toggleRename() {
if (!this.isEditable) {
return;
}
this.isRenaming = !this.isRenaming;
}
public onKeyDown(keyCode: number) {
if (keyCode === ESCAPE_KEY) {
this.toggleRename();
public rename() {
if (!this.isEditable) {
return;
}
}
public rename() {
const value = this.renameForm.submit();
if (value) {
@ -117,6 +123,12 @@ export class ClientComponent implements OnChanges {
public trackByRole(index: number, role: RoleDto) {
return role.name;
}
public onKeyDown(keyCode: number) {
if (keyCode === ESCAPE_KEY) {
this.toggleRename();
}
}
}
function connectHttpText(apiUrl: ApiUrlConfig, app: string, client: { id: string, secret: string }) {

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

@ -26,7 +26,7 @@
</sqx-client>
</ng-container>
<div class="table-items-footer">
<div class="table-items-footer" *ngIf="clientsState.links | async | sqxHasLink:'create'">
<form [formGroup]="addClientForm.form" (ngSubmit)="attachClient()">
<div class="row no-gutters">
<div class="col">

12
src/Squidex/app/framework/angular/http/http-extensions.ts

@ -17,37 +17,37 @@ import {
} from '@app/framework/internal';
export module HTTP {
export function getVersioned<T>(http: HttpClient, url: string, version?: Version): Observable<Versioned<HttpResponse<T>>> {
export function getVersioned<T = any>(http: HttpClient, url: string, version?: Version): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
return handleVersion(http.get<T>(url, { observe: 'response', headers }));
}
export function postVersioned<T>(http: HttpClient, url: string, body: any, version?: Version): Observable<Versioned<HttpResponse<T>>> {
export function postVersioned<T = any>(http: HttpClient, url: string, body: any, version?: Version): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
return handleVersion(http.post<T>(url, body, { observe: 'response', headers }));
}
export function putVersioned<T>(http: HttpClient, url: string, body: any, version?: Version): Observable<Versioned<HttpResponse<T>>> {
export function putVersioned<T = any>(http: HttpClient, url: string, body: any, version?: Version): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
return handleVersion(http.put<T>(url, body, { observe: 'response', headers }));
}
export function patchVersioned<T>(http: HttpClient, url: string, body: any, version?: Version): Observable<Versioned<HttpResponse<T>>> {
export function patchVersioned<T = any>(http: HttpClient, url: string, body: any, version?: Version): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
return handleVersion(http.request<T>('PATCH', url, { body, observe: 'response', headers }));
}
export function deleteVersioned<T>(http: HttpClient, url: string, version?: Version): Observable<Versioned<HttpResponse<T>>> {
export function deleteVersioned<T = any>(http: HttpClient, url: string, version?: Version): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
return handleVersion(http.delete<T>(url, { observe: 'response', headers }));
}
export function requestVersioned<T>(http: HttpClient, method: string, url: string, version?: Version, body?: any): Observable<Versioned<HttpResponse<T>>> {
export function requestVersioned<T = any>(http: HttpClient, method: string, url: string, version?: Version, body?: any): Observable<Versioned<HttpResponse<T>>> {
const headers = createHeaders(version);
return handleVersion(http.request<T>(method, url, { observe: 'response', headers, body }));

4
src/Squidex/app/shared/services/app-languages.service.ts

@ -24,7 +24,9 @@ import {
} from '@app/framework';
export type AppLanguagesDto = Versioned<AppLanguagesPayload>;
export type AppLanguagesPayload = { items: AppLanguageDto[] } & Resource;
export type AppLanguagesPayload = {
readonly items: AppLanguageDto[]
} & Resource;
export class AppLanguageDto {
public readonly _links: ResourceLinks = {};

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

@ -66,7 +66,7 @@ export class AppsService {
public postApp(dto: CreateAppDto): Observable<AppDto> {
const url = this.apiUrl.buildUrl('api/apps');
return this.http.post<any>(url, dto).pipe(
return this.http.post(url, dto).pipe(
map(body => {
return parseApp(body);
}),

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

@ -117,7 +117,7 @@ export class AssetsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets?${fullQuery}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
map(({ payload }) => {
const { total, items } = <{ total: number, items: any[] }>payload.body;
@ -133,7 +133,7 @@ export class AssetsService {
const req = new HttpRequest('POST', url, getFormData(file), { reportProgress: true });
return this.http.request<any>(req).pipe(
return this.http.request(req).pipe(
filter(event =>
event.type === HttpEventType.UploadProgress ||
event.type === HttpEventType.Response),
@ -166,7 +166,7 @@ export class AssetsService {
public getAsset(appName: string, id: string): Observable<AssetDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
map(({ payload }) => {
const body = payload.body;

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

@ -82,7 +82,7 @@ export class BackupsService {
public getRestore(): Observable<RestoreDto | null> {
const url = this.apiUrl.buildUrl(`api/apps/restore`);
return this.http.get<any>(url).pipe(
return this.http.get(url).pipe(
map(body => {
const restore = parseRestore(body);

123
src/Squidex/app/shared/services/clients.service.spec.ts

@ -14,8 +14,11 @@ import {
ApiUrlConfig,
ClientDto,
ClientsDto,
ClientsPayload,
ClientsService,
Version
Resource,
Version,
withLinks
} from '@app/shared/internal';
describe('ClientsService', () => {
@ -52,32 +55,13 @@ describe('ClientsService', () => {
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull();
req.flush([
{
id: 'client1',
name: 'Client 1',
secret: 'secret1',
role: 'Editor'
},
{
id: 'client2',
name: 'Client 2',
secret: 'secret2',
role: 'Developer'
}
], {
req.flush(clientsResponse(1, 2), {
headers: {
etag: '2'
}
});
expect(clients!).toEqual({
payload: [
new ClientDto('client1', 'Client 1', 'secret1', 'Editor'),
new ClientDto('client2', 'Client 2', 'secret2', 'Developer')
],
version: new Version('2')
});
expect(clients!).toEqual({ payload: createClients(1, 2), version: new Version('2') });
}));
it('should make post request to create client',
@ -85,10 +69,10 @@ describe('ClientsService', () => {
const dto = { id: 'client1' };
let client: ClientDto;
let clients: ClientsDto;
clientsService.postClient('my-app', dto, version).subscribe(result => {
client = result.payload;
clients = result;
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/clients');
@ -96,9 +80,13 @@ describe('ClientsService', () => {
expect(req.request.method).toEqual('POST');
expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({ id: 'client1', name: 'Client 1', secret: 'secret1', role: 'Developer' });
req.flush(clientsResponse(1, 2), {
headers: {
etag: '2'
}
});
expect(client!).toEqual(new ClientDto('client1', 'Client 1', 'secret1', 'Developer'));
expect(clients!).toEqual({ payload: createClients(1, 2), version: new Version('2') });
}));
it('should make put request to rename client',
@ -106,27 +94,59 @@ describe('ClientsService', () => {
const dto = { name: 'New Name' };
clientsService.putClient('my-app', 'client1', dto, version).subscribe();
const resource: Resource = {
_links: {
'update': { method: 'PUT', href: '/api/apps/my-app/clients/client1' }
}
};
let clients: ClientsDto;
clientsService.putClient('my-app', resource, dto, version).subscribe(result => {
clients = result;
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/clients/client1');
expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({});
req.flush(clientsResponse(1, 2), {
headers: {
etag: '2'
}
});
expect(clients!).toEqual({ payload: createClients(1, 2), version: new Version('2') });
}));
it('should make delete request to remove client',
inject([ClientsService, HttpTestingController], (clientsService: ClientsService, httpMock: HttpTestingController) => {
clientsService.deleteClient('my-app', 'client1', version).subscribe();
const resource: Resource = {
_links: {
'delete': { method: 'DELETE', href: '/api/apps/my-app/clients/client1' }
}
};
let clients: ClientsDto;
clientsService.deleteClient('my-app', resource, version).subscribe(result => {
clients = result;
});
const req = httpMock.expectOne('http://service/p/api/apps/my-app/clients/client1');
expect(req.request.method).toEqual('DELETE');
expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({});
req.flush(clientsResponse(1, 2), {
headers: {
etag: '2'
}
});
expect(clients!).toEqual({ payload: createClients(1, 2), version: new Version('2') });
}));
it('should make form request to create token',
@ -149,4 +169,45 @@ describe('ClientsService', () => {
expect(accessTokenDto!).toEqual(new AccessTokenDto('token1', 'type1'));
}));
});
function clientsResponse(...ids: number[]) {
return {
contributors: ids.map(id => ({
id: `id${id}`,
name: `Client ${id}`,
role: `Role${id}`,
secret: `secret${id}`,
_links: {
update: { method: 'PUT', href: `/clients/id${id}` }
}
})),
maxContributors: ids.length * 13,
_links: {
create: { method: 'POST', href: '/contributors' }
},
_meta: {
isInvited: 'true'
}
};
}
});
export function createClients(...ids: number[]): ClientsPayload {
return {
items: ids.map(id =>
withLinks(
new ClientDto(`id${id}`, `Client ${id}`, `secret${id}`, `Role${id}`),
{
_links: {
update: { method: 'PUT', href: `/clients/id${id}` }
}
}
)),
_links: {
create: { method: 'POST', href: '/clients' }
},
_meta: {
isInvited: 'true'
}
};
}

80
src/Squidex/app/shared/services/clients.service.ts

@ -15,22 +15,28 @@ import {
ApiUrlConfig,
HTTP,
mapVersioned,
Model,
pretifyError,
Resource,
ResourceLinks,
Version,
Versioned
Versioned,
withLinks
} from '@app/framework';
export type ClientsDto = Versioned<ClientDto[]>;
export type ClientsDto = Versioned<ClientsPayload>;
export type ClientsPayload = {
readonly items: ClientDto[]
} & Resource;
export class ClientDto {
public readonly _links: ResourceLinks = {};
export class ClientDto extends Model<ClientDto> {
constructor(
public readonly id: string,
public readonly name: string,
public readonly secret: string,
public readonly role = 'Developer'
public readonly role: string
) {
super();
}
}
@ -63,34 +69,19 @@ export class ClientsService {
public getClients(appName: string): Observable<ClientsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
mapVersioned(({ body }) => {
const items: any[] = body;
const clients = items.map(item =>
new ClientDto(
item.id,
item.name || item.id,
item.secret,
item.role));
return clients;
return parseClients(body);
}),
pretifyError('Failed to load clients. Please reload.'));
}
public postClient(appName: string, dto: CreateClientDto, version: Version): Observable<Versioned<ClientDto>> {
public postClient(appName: string, dto: CreateClientDto, version: Version): Observable<ClientsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`);
return HTTP.postVersioned<any>(this.http, url, dto, version).pipe(
return HTTP.postVersioned(this.http, url, dto, version).pipe(
mapVersioned(({ body }) => {
const client = new ClientDto(
body.id,
body.name || body.id,
body.secret,
body.role);
return client;
return parseClients(body);
}),
tap(() => {
this.analytics.trackEvent('Client', 'Created', appName);
@ -98,20 +89,30 @@ export class ClientsService {
pretifyError('Failed to add client. Please reload.'));
}
public putClient(appName: string, id: string, dto: UpdateClientDto, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`);
public putClient(appName: string, resource: Resource, dto: UpdateClientDto, version: Version): Observable<ClientsDto> {
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 parseClients(body);
}),
tap(() => {
this.analytics.trackEvent('Client', 'Updated', appName);
}),
pretifyError('Failed to revoke client. Please reload.'));
}
public deleteClient(appName: string, id: string, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients/${id}`);
public deleteClient(appName: string, resource: Resource, version: Version): Observable<ClientsDto> {
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 parseClients(body);
}),
tap(() => {
this.analytics.trackEvent('Client', 'Deleted', appName);
}),
@ -135,4 +136,19 @@ export class ClientsService {
}),
pretifyError('Failed to create token. Please retry.'));
}
}
function parseClients(response: any): ClientsPayload {
const items: any[] = response;
const clients = items.map(item =>
withLinks(
new ClientDto(
item.id,
item.name || item.id,
item.secret,
item.role),
item));
return withLinks({ items: clients, _links: {} }, response);
}

8
src/Squidex/app/shared/services/contents.service.ts

@ -101,7 +101,7 @@ export class ContentsService {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?${fullQuery}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
map(({ payload }) => {
const body = payload.body;
@ -132,7 +132,7 @@ export class ContentsService {
public getContent(appName: string, schemaName: string, id: string): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
map(({ version, payload }) => {
const body = payload.body;
@ -160,7 +160,7 @@ export class ContentsService {
public getVersionData(appName: string, schemaName: string, id: string, version: Version): Observable<Versioned<any>> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/${version.value}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
mapVersioned(({ body }) => {
return body;
}),
@ -170,7 +170,7 @@ export class ContentsService {
public postContent(appName: string, schemaName: string, dto: any, publish: boolean): Observable<ContentDto> {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?publish=${publish}`);
return HTTP.postVersioned<any>(this.http, url, dto).pipe(
return HTTP.postVersioned(this.http, url, dto).pipe(
map(({ version, payload }) => {
const body = payload.body;

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

@ -138,7 +138,7 @@ describe('ContributorsService', () => {
export function createContributors(...ids: number[]): ContributorsPayload {
return {
contributors: ids.map(id =>
items: ids.map(id =>
withLinks(
new ContributorDto(`id${id}`, id % 2 === 0 ? 'Owner' : 'Developer'),
{

10
src/Squidex/app/shared/services/contributors.service.ts

@ -25,7 +25,7 @@ import {
export type ContributorsDto = Versioned<ContributorsPayload>;
export type ContributorsPayload = {
readonly contributors: ContributorDto[],
readonly items: ContributorDto[],
readonly maxContributors: number
} & Resource;
@ -57,7 +57,7 @@ export class ContributorsService {
public getContributors(appName: string): Observable<ContributorsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
mapVersioned(({ body }) => {
return parseContributors(body);
}),
@ -95,8 +95,8 @@ export class ContributorsService {
}
}
function parseContributors(body: any) {
const items: any[] = body.contributors;
function parseContributors(response: any) {
const items: any[] = response.contributors;
const contributors = items.map(item =>
withLinks(
@ -105,5 +105,5 @@ function parseContributors(body: any) {
item.role),
item));
return withLinks({ contributors, maxContributors: body.maxContributors, _links: {} }, body);
return withLinks({ items: contributors, maxContributors: response.maxContributors, _links: {} }, response);
}

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

@ -52,7 +52,7 @@ export class PatternsService {
public getPatterns(appName: string): Observable<PatternsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
mapVersioned(({ body }) => {
const items: any[] = body;
@ -72,7 +72,7 @@ export class PatternsService {
public postPattern(appName: string, dto: EditPatternDto, version: Version): Observable<Versioned<PatternDto>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns`);
return HTTP.postVersioned<any>(this.http, url, dto, version).pipe(
return HTTP.postVersioned(this.http, url, dto, version).pipe(
mapVersioned(({ body }) => {
const pattern = new PatternDto(
body.patternId,

4
src/Squidex/app/shared/services/plans.service.ts

@ -63,7 +63,7 @@ export class PlansService {
public getPlans(appName: string): Observable<PlansDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/plans`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
mapVersioned(({ body }) => {
const items: any[] = body.plans;
@ -93,7 +93,7 @@ export class PlansService {
public putPlan(appName: string, dto: ChangePlanDto, version: Version): Observable<Versioned<PlanChangedDto>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/plan`);
return HTTP.putVersioned<any>(this.http, url, dto, version).pipe(
return HTTP.putVersioned(this.http, url, dto, version).pipe(
mapVersioned(payload => {
return <PlanChangedDto>payload.body;
}),

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

@ -54,7 +54,7 @@ export class RolesService {
public getRoles(appName: string): Observable<RolesDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/roles`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
mapVersioned(({ body }) => {
const items: any[] = body.roles;
@ -73,7 +73,7 @@ export class RolesService {
public postRole(appName: string, dto: CreateRoleDto, version: Version): Observable<Versioned<RoleDto>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/roles`);
return HTTP.postVersioned<any>(this.http, url, dto, version).pipe(
return HTTP.postVersioned(this.http, url, dto, version).pipe(
mapVersioned(() => {
const role = new RoleDto(dto.name, 0, 0, []);

8
src/Squidex/app/shared/services/rules.service.ts

@ -140,7 +140,7 @@ export class RulesService {
public getActions(): Observable<{ [name: string]: RuleElementDto }> {
const url = this.apiUrl.buildUrl('api/rules/actions');
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
map(({ payload }) => {
const items: { [name: string]: any } = payload.body;
@ -176,7 +176,7 @@ export class RulesService {
public getRules(appName: string): Observable<RulesDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
map(({ payload }) => {
const items: any[] = payload.body.items;
@ -190,7 +190,7 @@ export class RulesService {
public postRule(appName: string, dto: UpsertRuleDto): Observable<RuleDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules`);
return HTTP.postVersioned<any>(this.http, url, dto).pipe(
return HTTP.postVersioned(this.http, url, dto).pipe(
map(({ payload }) => {
return parseRule(payload.body);
}),
@ -263,7 +263,7 @@ export class RulesService {
public getEvents(appName: string, take: number, skip: number): Observable<RuleEventsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/events?take=${take}&skip=${skip}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
map(({ payload }) => {
const body = payload.body;

8
src/Squidex/app/shared/services/schemas.service.ts

@ -216,7 +216,7 @@ export class SchemasService {
public getSchemas(appName: string): Observable<SchemaDto[]> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
map(({ payload }) => {
const body = payload.body;
@ -244,7 +244,7 @@ export class SchemasService {
public getSchema(appName: string, id: string): Observable<SchemaDetailsDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${id}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
return HTTP.getVersioned(this.http, url).pipe(
map(({ version, payload }) => {
const body = payload.body;
@ -307,7 +307,7 @@ export class SchemasService {
public postSchema(appName: string, dto: CreateSchemaDto): Observable<Versioned<SchemaCreatedDto>> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`);
return HTTP.postVersioned<any>(this.http, url, dto).pipe(
return HTTP.postVersioned(this.http, url, dto).pipe(
mapVersioned(({ body }) => body!),
tap(() => {
this.analytics.trackEvent('Schema', 'Created', appName);
@ -388,7 +388,7 @@ export class SchemasService {
public postField(appName: string, schemaName: string, dto: AddFieldDto, parentId: number | undefined, version: Version): Observable<Versioned<RootFieldDto | NestedFieldDto>> {
const url = this.buildUrl(appName, schemaName, parentId, '');
return HTTP.postVersioned<any>(this.http, url, dto, version).pipe(
return HTTP.postVersioned(this.http, url, dto, version).pipe(
mapVersioned(({ body }) => {
if (parentId) {
const field = new NestedFieldDto(body.id, dto.name, dto.properties, parentId);

46
src/Squidex/app/shared/state/clients.state.spec.ts

@ -9,13 +9,14 @@ import { of } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq';
import {
ClientDto,
ClientsService,
ClientsState,
DialogService,
versioned
} from '@app/shared/internal';
import { createClients } from '../services/clients.service.spec';
import { TestValues } from './_test-helpers';
describe('ClientsState', () => {
@ -26,10 +27,7 @@ describe('ClientsState', () => {
version
} = TestValues;
const oldClients = [
new ClientDto('id1', 'name1', 'secret1'),
new ClientDto('id2', 'name2', 'secret2')
];
const oldClients = createClients(1, 2);
let dialogs: IMock<DialogService>;
let clientsService: IMock<ClientsService>;
@ -53,7 +51,7 @@ describe('ClientsState', () => {
clientsState.load().subscribe();
expect(clientsState.snapshot.clients.values).toEqual(oldClients);
expect(clientsState.snapshot.clients.values).toEqual(oldClients.items);
expect(clientsState.snapshot.version).toEqual(version);
expect(clientsState.isLoaded).toBeTruthy();
@ -80,21 +78,23 @@ describe('ClientsState', () => {
clientsState.load().subscribe();
});
it('should add client to snapshot when created', () => {
const newClient = new ClientDto('id3', 'name3', 'secret3');
it('should update clients when client added', () => {
const updated = createClients(1, 2, 3);
const request = { id: 'id3' };
clientsService.setup(x => x.postClient(app, request, version))
.returns(() => of(versioned(newVersion, newClient))).verifiable();
.returns(() => of(versioned(newVersion, updated))).verifiable();
clientsState.attach(request).subscribe();
expect(clientsState.snapshot.clients.values).toEqual([...oldClients, newClient]);
expect(clientsState.snapshot.clients.values).toEqual(updated.items);
expect(clientsState.snapshot.version).toEqual(newVersion);
});
it('should update properties when role updated', () => {
it('should update clients when role updated', () => {
const updated = createClients(1, 2, 3);
const request = { role: 'Owner' };
clientsService.setup(x => x.putClient(app, oldClients[0].id, request, version))
@ -102,35 +102,33 @@ describe('ClientsState', () => {
clientsState.update(oldClients[0], request).subscribe();
const client_1 = clientsState.snapshot.clients.at(0);
expect(client_1.name).toBe('name1');
expect(client_1.role).toBe('Owner');
expect(clientsState.snapshot.clients.values).toEqual(updated.items);
expect(clientsState.snapshot.version).toEqual(newVersion);
});
it('should update properties when name updated', () => {
it('should update clients when name updated', () => {
const updated = createClients(1, 2, 3);
const request = { name: 'NewName' };
clientsService.setup(x => x.putClient(app, oldClients[0].id, request, version))
clientsService.setup(x => x.putClient(app, oldClients.items[0], request, version))
.returns(() => of(versioned(newVersion))).verifiable();
clientsState.update(oldClients[0], request).subscribe();
const client_1 = clientsState.snapshot.clients.at(0);
expect(client_1.name).toBe('NewName');
expect(client_1.role).toBe('Developer');
expect(clientsState.snapshot.clients.values).toEqual(updated.items);
expect(clientsState.snapshot.version).toEqual(newVersion);
});
it('should remove client from snapshot when revoked', () => {
clientsService.setup(x => x.deleteClient(app, oldClients[0].id, version))
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();
clientsState.revoke(oldClients[0]).subscribe();
expect(clientsState.snapshot.clients.values).toEqual([oldClients[1]]);
expect(clientsState.snapshot.clients.values).toEqual(updated.items);
expect(clientsState.snapshot.version).toEqual(newVersion);
});
});

65
src/Squidex/app/shared/state/clients.state.ts

@ -14,8 +14,7 @@ import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
mapVersioned,
shareMapSubscribed,
ResourceLinks,
shareSubscribed,
State,
Version
@ -25,6 +24,7 @@ import { AppsState } from './apps.state';
import {
ClientDto,
ClientsPayload,
ClientsService,
CreateClientDto,
UpdateClientDto
@ -39,6 +39,9 @@ interface Snapshot {
// Indicates if the clients are loaded.
isLoaded?: boolean;
// The links.
links: ResourceLinks;
}
type ClientsList = ImmutableArray<ClientDto>;
@ -53,12 +56,16 @@ export class ClientsState extends State<Snapshot> {
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
public links =
this.changes.pipe(map(x => x.links),
distinctUntilChanged());
constructor(
private readonly clientsService: ClientsService,
private readonly appsState: AppsState,
private readonly dialogs: DialogService
) {
super({ clients: ImmutableArray.empty(), version: new Version('') });
super({ clients: ImmutableArray.empty(), version: new Version(''), links: {} });
}
public load(isReload = false): Observable<any> {
@ -72,50 +79,41 @@ export class ClientsState extends State<Snapshot> {
this.dialogs.notifyInfo('Clients reloaded.');
}
const clients = ImmutableArray.of(payload);
this.next(s => {
return { ...s, clients, isLoaded: true, version };
});
this.replaceClients(payload, version);
}),
shareSubscribed(this.dialogs));
}
public attach(request: CreateClientDto): Observable<ClientDto> {
public attach(request: CreateClientDto): Observable<any> {
return this.clientsService.postClient(this.appName, request, this.version).pipe(
tap(({ version, payload }) => {
this.next(s => {
const clients = s.clients.push(payload);
return { ...s, clients, version: version };
});
this.replaceClients(payload, version);
}),
shareMapSubscribed(this.dialogs, x => x.payload));
shareSubscribed(this.dialogs));
}
public revoke(client: ClientDto): Observable<any> {
return this.clientsService.deleteClient(this.appName, client.id, this.version).pipe(
tap(({ version }) => {
this.next(s => {
const clients = s.clients.filter(c => c.id !== client.id);
return { ...s, clients, version };
});
return this.clientsService.deleteClient(this.appName, client, this.version).pipe(
tap(({ version, payload }) => {
this.replaceClients(payload, version);
}),
shareSubscribed(this.dialogs));
}
public update(client: ClientDto, request: UpdateClientDto): Observable<ClientDto> {
return this.clientsService.putClient(this.appName, client.id, request, this.version).pipe(
mapVersioned(() => update(client, request)),
public update(client: ClientDto, request: UpdateClientDto): Observable<any> {
return this.clientsService.putClient(this.appName, client, request, this.version).pipe(
tap(({ version, payload }) => {
this.next(s => {
const clients = s.clients.replaceBy('id', payload);
return { ...s, clients, version };
});
this.replaceClients(payload, version);
}),
shareMapSubscribed(this.dialogs, x => x.payload));
shareSubscribed(this.dialogs));
}
private replaceClients(payload: ClientsPayload, version: Version) {
const clients = ImmutableArray.of(payload.items);
this.next(s => {
return { ...s, clients, isLoaded: true, version, links: payload._links };
});
}
private get appName() {
@ -125,7 +123,4 @@ export class ClientsState extends State<Snapshot> {
private get version() {
return this.snapshot.version;
}
}
const update = (client: ClientDto, request: UpdateClientDto) =>
client.with({ name: request.name || client.name, role: request.role || client.role });
}

10
src/Squidex/app/shared/state/contributors.state.spec.ts

@ -51,7 +51,7 @@ describe('ContributorsState', () => {
contributorsState.load().subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.contributors);
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.items);
expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.isLoaded).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors);
@ -90,7 +90,7 @@ describe('ContributorsState', () => {
contributorsState.assign(request).subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.contributors);
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.items);
expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors);
expect(contributorsState.snapshot.version).toEqual(newVersion);
});
@ -98,12 +98,12 @@ describe('ContributorsState', () => {
it('should update contributors when contribution revoked', () => {
const updated = createContributors(1, 2, 3);
contributorsService.setup(x => x.deleteContributor(app, oldContributors.contributors[0], version))
contributorsService.setup(x => x.deleteContributor(app, oldContributors.items[0], version))
.returns(() => of(versioned(newVersion, updated))).verifiable();
contributorsState.revoke(oldContributors.contributors[0]).subscribe();
contributorsState.revoke(oldContributors.items[0]).subscribe();
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.contributors);
expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.items);
expect(contributorsState.snapshot.maxContributors).toBe(oldContributors.maxContributors);
expect(contributorsState.snapshot.version).toEqual(newVersion);
});

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

@ -126,9 +126,9 @@ export class ContributorsState extends State<Snapshot> {
const maxContributors = payload.maxContributors || s.maxContributors;
const isLoaded = true;
const isMaxReached = maxContributors > 0 && maxContributors <= payload.contributors.length;
const isMaxReached = maxContributors > 0 && maxContributors <= payload.items.length;
const contributors = ImmutableArray.of(payload.contributors);
const contributors = ImmutableArray.of(payload.items);
return { ...s, contributors, maxContributors, isLoaded, isMaxReached, version: version, links: payload._links };
});

6
src/Squidex/app/shared/state/languages.state.spec.ts

@ -101,7 +101,7 @@ describe('LanguagesState', () => {
languagesState.load().subscribe();
});
it('should add language to snapshot when assigned', () => {
it('should update languages when language added', () => {
const updated = createLanguages('de');
languagesService.setup(x => x.postLanguage(app, It.isAny(), version))
@ -112,7 +112,7 @@ describe('LanguagesState', () => {
expectUpdated(updated);
});
it('should update language in snapshot when updated', () => {
it('should update languages when language updated', () => {
const updated = createLanguages('de');
const request = { isMaster: true, isOptional: false, fallback: [] };
@ -125,7 +125,7 @@ describe('LanguagesState', () => {
expectUpdated(updated);
});
it('should remove language from snapshot when deleted', () => {
it('should update languages when language deleted', () => {
const updated = createLanguages('de');
languagesService.setup(x => x.deleteLanguage(app, oldLanguages.items[1], version))

Loading…
Cancel
Save