diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs index 3327fc914..5e2454fab 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsByUserIndexCommandMiddleware.cs @@ -35,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes await Index(GetUserId(createApp)).AddAppAsync(createApp.AppId); break; case AssignContributor assignContributor: - await Index(GetUserId(context)).AddAppAsync(assignContributor.AppId); + await Index(GetUserId(assignContributor)).AddAppAsync(assignContributor.AppId); break; case RemoveContributor removeContributor: await Index(GetUserId(removeContributor)).RemoveAppAsync(removeContributor.AppId); @@ -57,19 +57,19 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes await next(); } - private static string GetUserId(RemoveContributor removeContributor) + private static string GetUserId(CreateApp createApp) { - return removeContributor.ContributorId; + return createApp.Actor.Identifier; } - private static string GetUserId(CreateApp createApp) + private static string GetUserId(AssignContributor assignContributor) { - return createApp.Actor.Identifier; + return assignContributor.ContributorId; } - private static string GetUserId(CommandContext context) + private static string GetUserId(RemoveContributor removeContributor) { - return context.Result>().IdOrValue; + return removeContributor.ContributorId; } private IAppsByUserIndex Index(string id) diff --git a/src/Squidex.Web/PermissionExtensions.cs b/src/Squidex.Web/PermissionExtensions.cs index 4bda25b58..ab63f14f8 100644 --- a/src/Squidex.Web/PermissionExtensions.cs +++ b/src/Squidex.Web/PermissionExtensions.cs @@ -39,7 +39,7 @@ namespace Squidex.Web public static bool HasPermission(this HttpContext httpContext, Permission permission, PermissionSet permissions = null) { - return httpContext.Permissions().Includes(permission) || permission?.Includes(permission) == true; + return httpContext.Permissions().Includes(permission) || permissions?.Includes(permission) == true; } public static bool HasPermission(this HttpContext httpContext, string id, string app = "*", string schema = "*", PermissionSet permissions = null) diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs index acc83e266..79769db43 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs @@ -61,7 +61,7 @@ namespace Squidex.Areas.Api.Controllers.Apps /// The name of the app. /// Contributor object that needs to be added to the app. /// - /// 200 => User assigned to app. + /// 201 => User assigned to app. /// 400 => User is already assigned to the app or not found. /// 404 => App not found. /// @@ -87,7 +87,7 @@ namespace Squidex.Areas.Api.Controllers.Apps response = ContributorsDto.FromApp(invited.App, appPlansProvider, this, true); } - return Ok(response); + return CreatedAtAction(nameof(GetContributors), new { app }, response); } /// diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs index 17ce8c320..ba55d5485 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.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; using Squidex.Infrastructure.Commands; @@ -39,12 +40,12 @@ namespace Squidex.Areas.Api.Controllers.Apps /// [HttpGet] [Route("apps/{app}/languages/")] - [ProducesResponseType(typeof(AppLanguageDto[]), 200)] + [ProducesResponseType(typeof(AppLanguagesDto), 200)] [ApiPermission(Permissions.AppCommon)] [ApiCosts(0)] public IActionResult GetLanguages(string app) { - var response = AppLanguageDto.FromApp(App); + var response = AppLanguagesDto.FromApp(App, this); Response.Headers[HeaderNames.ETag] = App.Version.ToString(); @@ -63,7 +64,7 @@ namespace Squidex.Areas.Api.Controllers.Apps /// [HttpPost] [Route("apps/{app}/languages/")] - [ProducesResponseType(typeof(AppLanguageDto), 201)] + [ProducesResponseType(typeof(AppLanguagesDto), 201)] [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppLanguagesCreate)] [ApiCosts(1)] @@ -71,9 +72,7 @@ namespace Squidex.Areas.Api.Controllers.Apps { var command = request.ToCommand(); - await CommandBus.PublishAsync(command); - - var response = AppLanguageDto.FromCommand(command); + var response = await InvokeCommandAsync(app, command); return CreatedAtAction(nameof(GetLanguages), new { app }, response); } @@ -85,19 +84,23 @@ namespace Squidex.Areas.Api.Controllers.Apps /// The language to update. /// The language object. /// - /// 204 => Language updated. + /// 200 => Language updated. /// 400 => Language request not valid. /// 404 => Language or app not found. /// [HttpPut] [Route("apps/{app}/languages/{language}/")] + [ProducesResponseType(typeof(AppLanguagesDto), 200)] + [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppLanguagesUpdate)] [ApiCosts(1)] - public async Task Update(string app, string language, [FromBody] UpdateLanguageDto request) + public async Task PutLanguage(string app, string language, [FromBody] UpdateLanguageDto request) { - await CommandBus.PublishAsync(request.ToCommand(ParseLanguage(language))); + var command = request.ToCommand(ParseLanguage(language)); + + var response = await InvokeCommandAsync(app, command); - return NoContent(); + return Ok(response); } /// @@ -106,18 +109,31 @@ namespace Squidex.Areas.Api.Controllers.Apps /// The name of the app. /// The language to delete from the app. /// - /// 204 => Language deleted. + /// 200 => Language deleted. /// 404 => Language or app not found. /// [HttpDelete] [Route("apps/{app}/languages/{language}/")] + [ProducesResponseType(typeof(AppLanguagesDto), 200)] [ApiPermission(Permissions.AppLanguagesDelete)] [ApiCosts(1)] public async Task DeleteLanguage(string app, string language) { - await CommandBus.PublishAsync(new RemoveLanguage { Language = ParseLanguage(language) }); + var command = new RemoveLanguage { Language = ParseLanguage(language) }; + + var response = await InvokeCommandAsync(app, command); + + return Ok(response); + } + + private async Task InvokeCommandAsync(string app, ICommand command) + { + var context = await CommandBus.PublishAsync(command); + + var result = context.Result(); + var response = AppLanguagesDto.FromApp(result, this); - return NoContent(); + return response; } private static Language ParseLanguage(string language) diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs index 62501e4c2..4ac3d8ad7 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguageDto.cs @@ -5,18 +5,18 @@ // 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; -using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; +using Squidex.Shared; +using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models { - public sealed class AppLanguageDto + public sealed class AppLanguageDto : Resource { /// /// The iso code of the language. @@ -46,25 +46,37 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// public bool IsOptional { get; set; } - public static AppLanguageDto FromCommand(AddLanguage command) + public static AppLanguageDto FromLanguage(LanguageConfig language, IAppEntity app, ApiController controller) { - return SimpleMapper.Map(command.Language, new AppLanguageDto { Fallback = Array.Empty() }); - } + var result = SimpleMapper.Map(language.Language, + new AppLanguageDto + { + IsMaster = language == app.LanguagesConfig.Master, + IsOptional = language.IsOptional, + Fallback = language.LanguageFallbacks.ToArray() + }); - public static AppLanguageDto[] FromApp(IAppEntity app) - { - return app.LanguagesConfig.OfType().Select(x => FromLanguage(x, app)).OrderByDescending(x => x.IsMaster).ThenBy(x => x.Iso2Code).ToArray(); + return CreateLinks(result, controller, app); } - private static AppLanguageDto FromLanguage(LanguageConfig x, IAppEntity app) + private static AppLanguageDto CreateLinks(AppLanguageDto result, ApiController controller, IAppEntity app) { - return SimpleMapper.Map(x.Language, - new AppLanguageDto + var values = new { app = app.Name, language = result.Iso2Code }; + + if (!result.IsMaster) + { + if (controller.HasPermission(Permissions.AppLanguagesUpdate, app.Name)) { - IsMaster = x == app.LanguagesConfig.Master, - IsOptional = x.IsOptional, - Fallback = x.LanguageFallbacks.ToArray() - }); + result.AddPutLink("update", controller.Url(x => nameof(x.PutLanguage), values)); + } + + if (controller.HasPermission(Permissions.AppLanguagesDelete, app.Name) && app.LanguagesConfig.Count > 1) + { + result.AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteLanguage), values)); + } + } + + return result; } } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs new file mode 100644 index 000000000..250408598 --- /dev/null +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/AppLanguagesDto.cs @@ -0,0 +1,54 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// 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; +using Squidex.Shared; +using Squidex.Web; + +namespace Squidex.Areas.Api.Controllers.Apps.Models +{ + public sealed class AppLanguagesDto : Resource + { + /// + /// The languages. + /// + [Required] + public AppLanguageDto[] Items { get; set; } + + public static AppLanguagesDto FromApp(IAppEntity app, ApiController controller) + { + var result = new AppLanguagesDto + { + Items = app.LanguagesConfig.OfType() + .Select(x => AppLanguageDto.FromLanguage(x, app, controller)) + .OrderByDescending(x => x.IsMaster) + .ThenBy(x => x.Iso2Code) + .ToArray() + }; + + return CreateLinks(result, controller, app.Name); + } + + private static AppLanguagesDto CreateLinks(AppLanguagesDto result, ApiController controller, string app) + { + var values = new { app }; + + result.AddSelfLink(controller.Url(x => nameof(x.GetLanguages), values)); + + if (controller.HasPermission(Permissions.AppLanguagesCreate, app)) + { + result.AddPostLink("create", controller.Url(x => nameof(x.PostLanguage), values)); + } + + return result; + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs index 30814f92d..bdd9e8947 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorDto.cs @@ -7,6 +7,7 @@ using System; using System.ComponentModel.DataAnnotations; +using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -33,6 +34,19 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models private static ContributorDto CreateLinks(ContributorDto result, ApiController controller, string app) { + if (!controller.IsUser(result.ContributorId)) + { + if (controller.HasPermission(Permissions.AppContributorsAssign, app)) + { + result.AddPostLink("update", controller.Url(x => nameof(x.PostContributor), new { app })); + } + + if (controller.HasPermission(Permissions.AppContributorsRevoke, app)) + { + result.AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteContributor), new { app, id = result.ContributorId })); + } + } + return result; } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs index cbeaab92c..310efbea9 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs @@ -10,6 +10,7 @@ using Newtonsoft.Json; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Infrastructure; +using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -57,6 +58,15 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models private static ContributorsDto CreateLinks(ContributorsDto result, ApiController controller, string app) { + var values = new { app }; + + result.AddSelfLink(controller.Url(x => nameof(x.GetContributors), values)); + + if (controller.HasPermission(Permissions.AppContributorsAssign, app)) + { + result.AddPostLink("create", controller.Url(x => nameof(x.PostContributor), values)); + } + return result; } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs index 0a470ff6c..8c6d2e707 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/UpdateLanguageDto.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System.Collections.Generic; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; using Squidex.Infrastructure.Reflection; @@ -26,7 +27,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models /// /// Optional fallback languages. /// - public Language[] Fallback { get; set; } + public List Fallback { get; set; } public UpdateLanguage ToCommand(Language language) { diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index 9987e0e89..08763e341 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -179,7 +179,7 @@ namespace Squidex.Areas.Api.Controllers.Assets var command = new CreateAsset { File = assetFile }; - var response = await InvokeCommand(app, command); + var response = await InvokeCommandAsync(app, command); return CreatedAtAction(nameof(GetAsset), new { app, id = response.Id }, response); } @@ -210,7 +210,7 @@ namespace Squidex.Areas.Api.Controllers.Assets var command = new UpdateAsset { File = assetFile, AssetId = id }; - var response = await InvokeCommand(app, command); + var response = await InvokeCommandAsync(app, command); return Ok(response); } @@ -237,7 +237,7 @@ namespace Squidex.Areas.Api.Controllers.Assets { var command = request.ToCommand(id); - var response = await InvokeCommand(app, command); + var response = await InvokeCommandAsync(app, command); return Ok(response); } @@ -262,7 +262,7 @@ namespace Squidex.Areas.Api.Controllers.Assets return NoContent(); } - private async Task InvokeCommand(string app, AssetCommand command) + private async Task InvokeCommandAsync(string app, ICommand command) { var context = await CommandBus.PublishAsync(command); diff --git a/src/Squidex/app/declarations.d.ts b/src/Squidex/app/declarations.d.ts index d40cdbd10..43716dcf2 100644 --- a/src/Squidex/app/declarations.d.ts +++ b/src/Squidex/app/declarations.d.ts @@ -12,6 +12,8 @@ declare module 'progressbar.js'; declare module 'sortablejs' { export class Ref { public destroy(): any; + + public option(property: string, value: any): any; } export function create(element: any, options: any): Ref; diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.html b/src/Squidex/app/features/administration/pages/users/user-page.component.html index bec006082..2666ee37c 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.html +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.html @@ -16,7 +16,7 @@ - + diff --git a/src/Squidex/app/features/administration/pages/users/user-page.component.ts b/src/Squidex/app/features/administration/pages/users/user-page.component.ts index ee7498713..069c71789 100644 --- a/src/Squidex/app/features/administration/pages/users/user-page.component.ts +++ b/src/Squidex/app/features/administration/pages/users/user-page.component.ts @@ -24,7 +24,7 @@ import { templateUrl: './user-page.component.html' }) export class UserPageComponent extends ResourceOwner implements OnInit { - public canUpdate = false; + public isEditable = false; public user?: UserDto; public userForm = new UserForm(this.formBuilder); @@ -47,9 +47,9 @@ export class UserPageComponent extends ResourceOwner implements OnInit { if (selectedUser) { this.userForm.load(selectedUser); - this.canUpdate = hasLink(this.user, 'update'); + this.isEditable = hasLink(this.user, 'update'); - if (!this.canUpdate) { + if (!this.isEditable) { this.userForm.form.disable(); } } @@ -57,7 +57,7 @@ export class UserPageComponent extends ResourceOwner implements OnInit { } public save() { - if (!this.canUpdate) { + if (!this.isEditable) { return; } diff --git a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html index ecb11c85b..9f0fde00f 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html @@ -106,12 +106,12 @@ - + - + diff --git a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts index fd7dcd668..a7e1e14a4 100644 --- a/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts @@ -36,7 +36,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit { public triggerType: string; public trigger: any = {}; - public canUpdate: boolean; + public isEditable: boolean; public step = 1; @@ -64,7 +64,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit { } public ngOnInit() { - this.canUpdate = !this.rule || hasLink(this.rule, 'update'); + this.isEditable = !this.rule || hasLink(this.rule, 'update'); if (this.mode === MODE_EDIT_ACTION) { this.step = 4; @@ -80,7 +80,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit { } public ngAfterViewInit() { - if (!this.canUpdate) { + if (!this.isEditable) { this.actionForm.form.disable(); this.triggerForm.form.disable(); @@ -145,7 +145,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit { } private updateTrigger() { - if (!this.canUpdate) { + if (!this.isEditable) { return; } @@ -160,7 +160,7 @@ export class RuleWizardComponent implements AfterViewInit, OnInit { } private updateAction() { - if (!this.canUpdate) { + if (!this.isEditable) { return; } diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html index d7c5bdc48..f1ae6a0f7 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html +++ b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html @@ -22,13 +22,13 @@ - @@ -38,12 +38,12 @@
-
- +
diff --git a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts index 0e18f523f..f169a8ff6 100644 --- a/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts +++ b/src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts @@ -30,7 +30,7 @@ export class ContentChangedTriggerComponent implements OnInit { public schemas: ImmutableArray; @Input() - public canUpdate: boolean; + public isEditable: boolean; @Input() public trigger: any; diff --git a/src/Squidex/app/features/settings/pages/languages/language.component.html b/src/Squidex/app/features/settings/pages/languages/language.component.html index d8dc7dccd..b8deb6e3c 100644 --- a/src/Squidex/app/features/settings/pages/languages/language.component.html +++ b/src/Squidex/app/features/settings/pages/languages/language.component.html @@ -13,7 +13,8 @@ - - +
-
+
-
+
-
+
{{language.englishName}}
-
+
@@ -56,7 +57,7 @@
-
+