diff --git a/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs b/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs index 6d969c204..dbcb2601f 100644 --- a/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs +++ b/src/Squidex/Areas/Api/Config/Swagger/XmlResponseTypesProcessor.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -26,20 +27,30 @@ namespace Squidex.Areas.Api.Config.Swagger { var operation = context.OperationDescription.Operation; - var returnsDescription = await context.MethodInfo.GetXmlDocumentationTagAsync("returns") ?? string.Empty; + var returnsDescription = await context.MethodInfo.GetXmlDocumentationTagAsync("returns"); - foreach (Match match in ResponseRegex.Matches(returnsDescription)) + if (!string.IsNullOrWhiteSpace(returnsDescription)) { - var statusCode = match.Groups["Code"].Value; - - if (!operation.Responses.TryGetValue(statusCode, out var response)) + foreach (Match match in ResponseRegex.Matches(returnsDescription)) { - response = new SwaggerResponse(); + var statusCode = match.Groups["Code"].Value; - operation.Responses[statusCode] = response; - } + if (!operation.Responses.TryGetValue(statusCode, out var response)) + { + response = new SwaggerResponse(); + + operation.Responses[statusCode] = response; + } - response.Description = match.Groups["Description"].Value; + var description = match.Groups["Description"].Value; + + if (description.Contains("=>")) + { + throw new InvalidOperationException("Description not formatted correcly."); + } + + response.Description = description; + } } await AddInternalErrorResponseAsync(context, operation); @@ -51,9 +62,19 @@ namespace Squidex.Areas.Api.Config.Swagger private static async Task AddInternalErrorResponseAsync(OperationProcessorContext context, SwaggerOperation operation) { + var errorSchema = await context.SchemaGenerator.GetErrorDtoSchemaAsync(context.SchemaResolver); + if (!operation.Responses.ContainsKey("500")) { - operation.AddResponse("500", "Operation failed", await context.SchemaGenerator.GetErrorDtoSchemaAsync(context.SchemaResolver)); + operation.AddResponse("500", "Operation failed", errorSchema); + } + + foreach (var (code, response) in operation.Responses) + { + if (code != "404" && code.StartsWith("4", StringComparison.OrdinalIgnoreCase) && response.Schema == null) + { + response.Schema = errorSchema; + } } } @@ -61,7 +82,9 @@ namespace Squidex.Areas.Api.Config.Swagger { foreach (var (code, response) in operation.Responses.ToList()) { - if (string.IsNullOrWhiteSpace(response.Description) || response.Description?.Contains("=>") == true) + if (string.IsNullOrWhiteSpace(response.Description) || + response.Description?.Contains("=>") == true || + response.Description?.Contains("=>") == true) { operation.Responses.Remove(code); } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs index 14c4ea1b6..b89697d26 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppClientsController.cs @@ -70,7 +70,6 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpPost] [Route("apps/{app}/clients/")] [ProducesResponseType(typeof(ClientsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppClientsCreate)] [ApiCosts(1)] public async Task PostClient(string app, [FromBody] CreateClientDto request) @@ -86,7 +85,7 @@ namespace Squidex.Areas.Api.Controllers.Apps /// Updates an app client. /// /// The name of the app. - /// The id of the client that must be updated. + /// The id of the client that must be updated. /// Client object that needs to be updated. /// /// 200 => Client updated. @@ -97,14 +96,13 @@ namespace Squidex.Areas.Api.Controllers.Apps /// Only the display name can be changed, create a new client if necessary. /// [HttpPut] - [Route("apps/{app}/clients/{clientId}/")] + [Route("apps/{app}/clients/{id}/")] [ProducesResponseType(typeof(ClientsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppClientsUpdate)] [ApiCosts(1)] - public async Task PutClient(string app, string clientId, [FromBody] UpdateClientDto request) + public async Task PutClient(string app, string id, [FromBody] UpdateClientDto request) { - var command = request.ToCommand(clientId); + var command = request.ToCommand(id); var response = await InvokeCommandAsync(command); @@ -115,7 +113,7 @@ namespace Squidex.Areas.Api.Controllers.Apps /// Revoke an app client. /// /// The name of the app. - /// The id of the client that must be deleted. + /// The id of the client that must be deleted. /// /// 200 => Client revoked. /// 404 => Client or app not found. @@ -124,13 +122,13 @@ namespace Squidex.Areas.Api.Controllers.Apps /// The application that uses this client credentials cannot access the API after it has been revoked. /// [HttpDelete] - [Route("apps/{app}/clients/{clientId}/")] + [Route("apps/{app}/clients/{id}/")] [ProducesResponseType(typeof(ClientsDto), 200)] [ApiPermission(Permissions.AppClientsDelete)] [ApiCosts(1)] - public async Task DeleteClient(string app, string clientId) + public async Task DeleteClient(string app, string id) { - var command = new RevokeClient { Id = clientId }; + var command = new RevokeClient { Id = id }; var response = await InvokeCommandAsync(command); diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs index d3922eae7..021afa123 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppContributorsController.cs @@ -68,7 +68,6 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpPost] [Route("apps/{app}/contributors/")] [ProducesResponseType(typeof(ContributorsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppContributorsAssign)] [ApiCosts(1)] public async Task PostContributor(string app, [FromBody] AssignContributorDto request) @@ -103,7 +102,6 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpDelete] [Route("apps/{app}/contributors/{id}/")] [ProducesResponseType(typeof(ContributorsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppContributorsRevoke)] [ApiCosts(1)] public async Task DeleteContributor(string app, string id) diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs index ed24f37bd..03064da7b 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppLanguagesController.cs @@ -65,7 +65,6 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpPost] [Route("apps/{app}/languages/")] [ProducesResponseType(typeof(AppLanguagesDto), 201)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppLanguagesCreate)] [ApiCosts(1)] public async Task PostLanguage(string app, [FromBody] AddLanguageDto request) @@ -91,7 +90,6 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpPut] [Route("apps/{app}/languages/{language}/")] [ProducesResponseType(typeof(AppLanguagesDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppLanguagesUpdate)] [ApiCosts(1)] public async Task PutLanguage(string app, string language, [FromBody] UpdateLanguageDto request) diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs index 15cd8f80a..022a20cab 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppPatternsController.cs @@ -67,7 +67,6 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpPost] [Route("apps/{app}/patterns/")] [ProducesResponseType(typeof(PatternsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppPatternsCreate)] [ApiCosts(1)] public async Task PostPattern(string app, [FromBody] UpdatePatternDto request) @@ -86,14 +85,13 @@ namespace Squidex.Areas.Api.Controllers.Apps /// The id of the pattern to be updated. /// Pattern to be updated for the app. /// - /// 204 => Pattern updated. + /// 200 => Pattern updated. /// 400 => Pattern request not valid. /// 404 => Pattern or app not found. /// [HttpPut] [Route("apps/{app}/patterns/{id}/")] [ProducesResponseType(typeof(PatternsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppPatternsUpdate)] [ApiCosts(1)] public async Task UpdatePattern(string app, Guid id, [FromBody] UpdatePatternDto request) diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs index 3ec75ce9c..d51daaf2d 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppRolesController.cs @@ -89,7 +89,6 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpPost] [Route("apps/{app}/roles/")] [ProducesResponseType(typeof(RolesDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppRolesCreate)] [ApiCosts(1)] public async Task PostRole(string app, [FromBody] AddRoleDto request) @@ -139,7 +138,6 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpDelete] [Route("apps/{app}/roles/{name}/")] [ProducesResponseType(typeof(RolesDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppRolesDelete)] [ApiCosts(1)] public async Task DeleteRole(string app, string name) diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 688893497..0535bc27c 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -85,8 +85,6 @@ namespace Squidex.Areas.Api.Controllers.Apps [HttpPost] [Route("apps/")] [ProducesResponseType(typeof(AppDto), 201)] - [ProducesResponseType(typeof(ErrorDto), 400)] - [ProducesResponseType(typeof(ErrorDto), 409)] [ApiPermission] [ApiCosts(1)] public async Task PostApp([FromBody] CreateAppDto request) diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs index df66388b1..a7a344251 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientDto.cs @@ -8,6 +8,7 @@ using System.ComponentModel.DataAnnotations; using Squidex.Domain.Apps.Core.Apps; using Squidex.Infrastructure.Reflection; +using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -46,6 +47,18 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models private ClientDto CreateLinks(ApiController controller, string app) { + var values = new { app, id = Id }; + + if (controller.HasPermission(Permissions.AppClientsUpdate, app)) + { + AddPutLink("update", controller.Url(x => nameof(x.PutClient), values)); + } + + if (controller.HasPermission(Permissions.AppClientsDelete, app)) + { + AddDeleteLink("delete", controller.Url(x => nameof(x.DeleteClient), values)); + } + return this; } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs index 33d470e75..7116e99f2 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ClientsDto.cs @@ -8,6 +8,7 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Shared; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Apps.Models @@ -32,6 +33,15 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models private ClientsDto CreateLinks(ApiController controller, string app) { + var values = new { app }; + + AddSelfLink(controller.Url(x => nameof(x.GetClients), values)); + + if (controller.HasPermission(Permissions.AppClientsCreate, app)) + { + AddPostLink("create", controller.Url(x => nameof(x.PostClient), values)); + } + return this; } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs index 653a2e229..9d8baf5e3 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs @@ -62,7 +62,7 @@ namespace Squidex.Areas.Api.Controllers.Apps.Models AddSelfLink(controller.Url(x => nameof(x.GetContributors), values)); - if (controller.HasPermission(Permissions.AppContributorsAssign, app) && Items.Length < MaxContributors) + if (controller.HasPermission(Permissions.AppContributorsAssign, app) && (MaxContributors < 0 || Items.Length < MaxContributors)) { AddPostLink("create", controller.Url(x => nameof(x.PostContributor), values)); } diff --git a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs index c88246a85..754d1de69 100644 --- a/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs @@ -200,7 +200,6 @@ namespace Squidex.Areas.Api.Controllers.Assets /// [HttpPut] [Route("apps/{app}/assets/{id}/content/")] - [ProducesResponseType(typeof(ErrorDto), 400)] [ProducesResponseType(typeof(AssetDto), 200)] [ApiPermission(Permissions.AppAssetsUpdate)] [ApiCosts(1)] @@ -228,7 +227,6 @@ namespace Squidex.Areas.Api.Controllers.Assets /// [HttpPut] [Route("apps/{app}/assets/{id}/")] - [ProducesResponseType(typeof(ErrorDto), 400)] [ProducesResponseType(typeof(AssetDto), 200)] [AssetRequestSizeLimit] [ApiPermission(Permissions.AppAssetsUpdate)] @@ -248,7 +246,7 @@ namespace Squidex.Areas.Api.Controllers.Assets /// The name of the app. /// The id of the asset to delete. /// - /// 204 => Asset has been deleted. + /// 204 => Asset deleted. /// 404 => Asset or app not found. /// [HttpDelete] diff --git a/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs b/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs index 5603ec207..6125003f5 100644 --- a/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Comments/CommentsController.cs @@ -76,7 +76,6 @@ namespace Squidex.Areas.Api.Controllers.Comments [HttpPost] [Route("apps/{app}/comments/{commentsId}")] [ProducesResponseType(typeof(EntityCreatedDto), 201)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppCommon)] [ApiCosts(0)] public async Task PostComment(string app, Guid commentsId, [FromBody] UpsertCommentDto request) @@ -104,7 +103,6 @@ namespace Squidex.Areas.Api.Controllers.Comments /// [HttpPut] [Route("apps/{app}/comments/{commentsId}/{commentId}")] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppCommon)] [ApiCosts(0)] public async Task PutComment(string app, Guid commentsId, Guid commentId, [FromBody] UpsertCommentDto request) @@ -126,7 +124,6 @@ namespace Squidex.Areas.Api.Controllers.Comments /// [HttpDelete] [Route("apps/{app}/comments/{commentsId}/{commentId}")] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppCommon)] [ApiCosts(0)] public async Task DeleteComment(string app, Guid commentsId, Guid commentId) diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index 2a893b573..079d9fdfc 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -117,6 +117,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpGet] [Route("content/{app}/")] + [ProducesResponseType(typeof(ContentsDto), 200)] [ApiPermission] [ApiCosts(1)] public async Task GetAllContents(string app, [FromQuery] string ids, [FromQuery] string status = null) @@ -153,6 +154,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpGet] [Route("content/{app}/{name}/")] + [ProducesResponseType(typeof(ContentsDto), 200)] [ApiPermission] [ApiCosts(1)] public async Task GetContents(string app, string name, [FromQuery] string ids = null, [FromQuery] string status = null) @@ -188,6 +190,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpGet] [Route("content/{app}/{name}/{id}/")] + [ProducesResponseType(typeof(ContentsDto), 200)] [ApiPermission] [ApiCosts(1)] public async Task GetContent(string app, string name, Guid id) @@ -260,6 +263,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpPost] [Route("content/{app}/{name}/")] + [ProducesResponseType(typeof(ContentsDto), 200)] [ApiPermission(Permissions.AppContentsCreate)] [ApiCosts(1)] public async Task PostContent(string app, string name, [FromBody] NamedContentData request, [FromQuery] bool publish = false) @@ -296,6 +300,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpPut] [Route("content/{app}/{name}/{id}/")] + [ProducesResponseType(typeof(ContentsDto), 200)] [ApiPermission(Permissions.AppContentsUpdate)] [ApiCosts(1)] public async Task PutContent(string app, string name, Guid id, [FromBody] NamedContentData request, [FromQuery] bool asDraft = false) @@ -327,6 +332,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpPatch] [Route("content/{app}/{name}/{id}/")] + [ProducesResponseType(typeof(ContentsDto), 200)] [ApiPermission(Permissions.AppContentsUpdate)] [ApiCosts(1)] public async Task PatchContent(string app, string name, Guid id, [FromBody] NamedContentData request, [FromQuery] bool asDraft = false) @@ -348,7 +354,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// The id of the content item to publish. /// The status request. /// - /// 204 => Content published. + /// 200 => Content published. /// 404 => Content, schema or app not found. /// 400 => Request is not valid. /// @@ -357,6 +363,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpPut] [Route("content/{app}/{name}/{id}/status/")] + [ProducesResponseType(typeof(ContentsDto), 200)] [ApiPermission] [ApiCosts(1)] public async Task PutContentStatus(string app, string name, Guid id, ChangeStatusDto request) @@ -382,7 +389,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// The name of the schema. /// The id of the content item to discard changes. /// - /// 204 => Content restored. + /// 200 => Content restored. /// 404 => Content, schema or app not found. /// 400 => Content was not archived. /// @@ -391,17 +398,18 @@ namespace Squidex.Areas.Api.Controllers.Contents /// [HttpPut] [Route("content/{app}/{name}/{id}/discard/")] + [ProducesResponseType(typeof(ContentsDto), 200)] [ApiPermission(Permissions.AppContentsDiscard)] [ApiCosts(1)] - public async Task DiscardChanges(string app, string name, Guid id) + public async Task DiscardDraft(string app, string name, Guid id) { await contentQuery.ThrowIfSchemaNotExistsAsync(Context(), name); var command = new DiscardChanges { ContentId = id }; - await CommandBus.PublishAsync(command); + var response = await InvokeCommandAsync(app, name, command); - return NoContent(); + return Ok(response); } /// @@ -411,7 +419,7 @@ namespace Squidex.Areas.Api.Controllers.Contents /// The name of the schema. /// The id of the content item to delete. /// - /// 204 => Content has been deleted. + /// 204 => Content deleted. /// 404 => Content, schema or app not found. /// /// @@ -427,9 +435,9 @@ namespace Squidex.Areas.Api.Controllers.Contents var command = new DeleteContent { ContentId = id }; - var response = await InvokeCommandAsync(app, name, command); + await CommandBus.PublishAsync(command); - return Ok(response); + return NoContent(); } private async Task InvokeCommandAsync(string app, string schema, ICommand command) diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs index 55b02caeb..d61283bdf 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs @@ -119,12 +119,12 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models { if (controller.HasPermission(Permissions.AppContentsDiscard, app, schema)) { - AddPutLink("changes/discard", controller.Url(x => nameof(x.DiscardChanges), values)); + AddPutLink("draft/discard", controller.Url(x => nameof(x.DiscardDraft), values)); } if (controller.HasPermission(Helper.StatusPermission(app, schema, Status.Published))) { - AddPutLink("changes/publish", controller.Url(x => nameof(x.PutContentStatus), values)); + AddPutLink("draft/publish", controller.Url(x => nameof(x.PutContentStatus), values)); } } @@ -134,7 +134,7 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models if (Status == Status.Published) { - AddPutLink("update/change", controller.Url(x => nameof(x.PutContent), values) + "?asDraft"); + AddPutLink("draft/propose", controller.Url(x => nameof(x.PutContent), values) + "?asDraft=true"); } } diff --git a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs index 5611ac7ac..a14f220a3 100644 --- a/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs +++ b/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs @@ -71,7 +71,6 @@ namespace Squidex.Areas.Api.Controllers.Plans [HttpPut] [Route("apps/{app}/plan/")] [ProducesResponseType(typeof(PlanChangedDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppPlansChange)] [ApiCosts(0)] public async Task PutPlan(string app, [FromBody] ChangePlanDto request) diff --git a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs index 3e1ddb232..ae94f4c81 100644 --- a/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs @@ -103,8 +103,7 @@ namespace Squidex.Areas.Api.Controllers.Rules /// [HttpPost] [Route("apps/{app}/rules/")] - [ProducesResponseType(typeof(EntityCreatedDto), 201)] - [ProducesResponseType(typeof(ErrorDto), 400)] + [ProducesResponseType(typeof(RuleDto), 201)] [ApiPermission(Permissions.AppRulesCreate)] [ApiCosts(1)] public async Task PostRule(string app, [FromBody] CreateRuleDto request) @@ -132,7 +131,6 @@ namespace Squidex.Areas.Api.Controllers.Rules /// [HttpPut] [Route("apps/{app}/rules/{id}/")] - [ProducesResponseType(typeof(ErrorDto), 200)] [ProducesResponseType(typeof(RuleDto), 400)] [ApiPermission(Permissions.AppRulesUpdate)] [ApiCosts(1)] @@ -157,8 +155,7 @@ namespace Squidex.Areas.Api.Controllers.Rules /// [HttpPut] [Route("apps/{app}/rules/{id}/enable/")] - [ProducesResponseType(typeof(ErrorDto), 200)] - [ProducesResponseType(typeof(RuleDto), 400)] + [ProducesResponseType(typeof(RuleDto), 200)] [ApiPermission(Permissions.AppRulesDisable)] [ApiCosts(1)] public async Task EnableRule(string app, Guid id) @@ -182,8 +179,7 @@ namespace Squidex.Areas.Api.Controllers.Rules /// [HttpPut] [Route("apps/{app}/rules/{id}/disable/")] - [ProducesResponseType(typeof(ErrorDto), 200)] - [ProducesResponseType(typeof(RuleDto), 400)] + [ProducesResponseType(typeof(RuleDto), 200)] [ApiPermission(Permissions.AppRulesDisable)] [ApiCosts(1)] public async Task DisableRule(string app, Guid id) @@ -201,7 +197,7 @@ namespace Squidex.Areas.Api.Controllers.Rules /// The name of the app. /// The id of the rule to delete. /// - /// 204 => Rule has been deleted. + /// 204 => Rule deleted. /// 404 => Rule or app not found. /// [HttpDelete] @@ -248,7 +244,7 @@ namespace Squidex.Areas.Api.Controllers.Rules /// The name of the app. /// The event to enqueue. /// - /// 200 => Rule enqueued. + /// 204 => Rule enqueued. /// 404 => App or rule event not found. /// [HttpPut] @@ -275,7 +271,7 @@ namespace Squidex.Areas.Api.Controllers.Rules /// The name of the app. /// The event to enqueue. /// - /// 200 => Rule deqeued. + /// 204 => Rule deqeued. /// 404 => App or rule event not found. /// [HttpDelete] diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs index 1ccb87745..55e68584b 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemaFieldsController.cs @@ -42,8 +42,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPost] [Route("apps/{app}/schemas/{name}/fields/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 409)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task PostField(string app, string name, [FromBody] AddFieldDto request) @@ -71,8 +69,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPost] [Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 409)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task PostNestedField(string app, string name, long parentId, [FromBody] AddFieldDto request) @@ -91,14 +87,13 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// The name of the schema. /// The request that contains the field ids. /// - /// 200 => Schema fields reorderd. + /// 200 => Schema fields reordered. /// 400 => Schema field ids do not cover the fields of the schema. /// 404 => Schema or app not found. /// [HttpPut] [Route("apps/{app}/schemas/{name}/fields/ordering/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task PutSchemaFieldOrdering(string app, string name, [FromBody] ReorderFieldsDto request) @@ -118,14 +113,13 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// The parent field id. /// The request that contains the field ids. /// - /// 200 => Schema fields reorderd. + /// 200 => Schema fields reordered. /// 400 => Schema field ids do not cover the fields of the schema. /// 404 => Schema, field or app not found. /// [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/ordering/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task PutNestedFieldOrdering(string app, string name, long parentId, [FromBody] ReorderFieldsDto request) @@ -152,7 +146,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{id:long}/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task PutField(string app, string name, long id, [FromBody] UpdateFieldDto request) @@ -180,7 +173,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task PutNestedField(string app, string name, long parentId, long id, [FromBody] UpdateFieldDto request) @@ -209,7 +201,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{id:long}/lock/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task LockField(string app, string name, long id) @@ -239,7 +230,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/lock/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task LockNestedField(string app, string name, long parentId, long id) @@ -268,7 +258,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{id:long}/hide/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task HideField(string app, string name, long id) @@ -298,7 +287,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/hide/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task HideNestedField(string app, string name, long parentId, long id) @@ -327,7 +315,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{id:long}/show/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task ShowField(string app, string name, long id) @@ -356,7 +343,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/show/")] - [ProducesResponseType(typeof(ErrorDto), 400)] + [ProducesResponseType(typeof(SchemaDetailsDto), 200)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task ShowNestedField(string app, string name, long parentId, long id) @@ -385,7 +372,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{id:long}/enable/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task EnableField(string app, string name, long id) @@ -415,7 +401,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/enable/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task EnableNestedField(string app, string name, long parentId, long id) @@ -444,7 +429,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{id:long}/disable/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task DisableField(string app, string name, long id) @@ -474,7 +458,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/disable/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task DisableNestedField(string app, string name, long parentId, long id) @@ -500,7 +483,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpDelete] [Route("apps/{app}/schemas/{name}/fields/{id:long}/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task DeleteField(string app, string name, long id) @@ -527,7 +509,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpDelete] [Route("apps/{app}/schemas/{name}/fields/{parentId:long}/nested/{id:long}/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task DeleteNestedField(string app, string name, long parentId, long id) diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index 032a834bf..40902d651 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -109,8 +109,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPost] [Route("apps/{app}/schemas/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] - [ProducesResponseType(typeof(ErrorDto), 409)] [ApiPermission(Permissions.AppSchemasCreate)] [ApiCosts(1)] public async Task PostSchema(string app, [FromBody] CreateSchemaDto request) @@ -129,14 +127,13 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// The name of the schema. /// The schema object that needs to updated. /// - /// 204 => Schema updated. + /// 200 => Schema updated. /// 400 => Schema properties are not valid. /// 404 => Schema or app not found. /// [HttpPut] [Route("apps/{app}/schemas/{name}/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task PutSchema(string app, string name, [FromBody] UpdateSchemaDto request) @@ -162,7 +159,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/sync")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task PutSchemaSync(string app, string name, [FromBody] SynchronizeSchemaDto request) @@ -187,7 +183,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/category")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task PutCategory(string app, string name, [FromBody] ChangeCategoryDto request) @@ -212,7 +207,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/preview-urls")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasUpdate)] [ApiCosts(1)] public async Task PutPreviewUrls(string app, string name, [FromBody] ConfigurePreviewUrlsDto request) @@ -225,7 +219,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas } /// - /// Update the scripts of a schema. + /// Update the scripts. /// /// The name of the app. /// The name of the schema. @@ -238,7 +232,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/scripts/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasScripts)] [ApiCosts(1)] public async Task PutScripts(string app, string name, [FromBody] SchemaScriptsDto request) @@ -263,7 +256,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/publish/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasPublish)] [ApiCosts(1)] public async Task PublishSchema(string app, string name) @@ -288,7 +280,6 @@ namespace Squidex.Areas.Api.Controllers.Schemas [HttpPut] [Route("apps/{app}/schemas/{name}/unpublish/")] [ProducesResponseType(typeof(SchemaDetailsDto), 200)] - [ProducesResponseType(typeof(ErrorDto), 400)] [ApiPermission(Permissions.AppSchemasPublish)] [ApiCosts(1)] public async Task UnpublishSchema(string app, string name) @@ -306,7 +297,7 @@ namespace Squidex.Areas.Api.Controllers.Schemas /// The name of the app. /// The name of the schema to delete. /// - /// 204 => Schema has been deleted. + /// 204 => Schema deleted. /// 404 => Schema or app not found. /// [HttpDelete] diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.html b/src/Squidex/app/features/content/pages/content/content-page.component.html index 10c7441ff..22454ce68 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.html +++ b/src/Squidex/app/features/content/pages/content/content-page.component.html @@ -45,13 +45,13 @@ - diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.ts b/src/Squidex/app/features/content/pages/content/content-page.component.ts index bf83ec832..39f263cd7 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.ts +++ b/src/Squidex/app/features/content/pages/content/content-page.component.ts @@ -124,7 +124,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD this.saveContent(true, false); } - public saveAsProposal() { + public saveAsDraft() { this.saveContent(false, true); } @@ -132,23 +132,27 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD this.saveContent(false, false); } - private saveContent(publish: boolean, asProposal: boolean) { - if (this.content && this.content.status === 'Archived') { - return; - } - + private saveContent(publish: boolean, asDraft: boolean) { const value = this.contentForm.submit(); if (value) { if (this.content) { - if (asProposal) { - this.contentsState.proposeUpdate(this.content, value) + if (asDraft) { + if (this.content && !this.content.canDraftPropose) { + return; + } + + this.contentsState.proposeDraft(this.content, value) .subscribe(() => { this.contentForm.submitCompleted({ noReset: true }); }, error => { this.contentForm.submitFailed(error); }); } else { + if (this.content && !this.content.canUpdate) { + return; + } + this.contentsState.update(this.content, value) .subscribe(() => { this.contentForm.submitCompleted({ noReset: true }); @@ -157,6 +161,10 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD }); } } else { + if ((publish && !this.contentsState.snapshot.canCreate) || (!publish && !this.contentsState.snapshot.canCreateAndPublish)) { + return; + } + this.contentsState.create(value, publish) .subscribe(() => { this.back(); @@ -178,7 +186,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD } public discardChanges() { - this.contentsState.discardChanges(this.content); + this.contentsState.discardDraft(this.content); } public delete() { @@ -190,7 +198,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD public publishChanges() { this.dueTimeSelector.selectDueTime('Publish').pipe( - switchMap(d => this.contentsState.publishChanges(this.content, d)), onErrorResumeNext()) + switchMap(d => this.contentsState.publishDraft(this.content, d)), onErrorResumeNext()) .subscribe(); } diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.html b/src/Squidex/app/features/content/pages/contents/contents-page.component.html index c419dfeb5..d0af013bc 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.html +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.html @@ -2,13 +2,7 @@ - - Archive - - - - Contents - + Contents @@ -36,8 +30,8 @@
-
- diff --git a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html index 61064cc9e..29ee47512 100644 --- a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html @@ -82,7 +82,7 @@
- + diff --git a/src/Squidex/app/framework/state.ts b/src/Squidex/app/framework/state.ts index ea87f8e86..97541026e 100644 --- a/src/Squidex/app/framework/state.ts +++ b/src/Squidex/app/framework/state.ts @@ -10,11 +10,10 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ErrorDto } from './utils/error'; - +import { ResourceLinks } from './utils/hateos'; import { Types } from './utils/types'; import { fullValue } from './angular/forms/forms-helper'; -import { ResourceLinks } from '@app/shared'; export interface FormState { submitted: boolean; diff --git a/src/Squidex/app/framework/utils/hateos.ts b/src/Squidex/app/framework/utils/hateos.ts index b6cab19cd..cf2bc3faa 100644 --- a/src/Squidex/app/framework/utils/hateos.ts +++ b/src/Squidex/app/framework/utils/hateos.ts @@ -1,3 +1,4 @@ + /* * Squidex Headless CMS * @@ -16,15 +17,17 @@ export type ResourceLink = { href: string; method: ResourceMethod; }; export type Metadata = { [rel: string]: string }; -function hasLink(value: Resource | ResourceLinks, rel: string): boolean { - const link = getLink(value, rel); +export function hasAnyLink(value: Resource | ResourceLinks, ...rels: string[]) { + if (!value) { + return false; + } - return !!(link && link.method && link.href); -} + const links = value._links || value; -export function hasAnyLink(value: Resource | ResourceLinks, ...rels: string[]) { for (let rel of rels) { - if (hasLink(value, rel)) { + const link = links[rel]; + + if (link && link.method && link.href) { return true; } } @@ -32,10 +35,6 @@ export function hasAnyLink(value: Resource | ResourceLinks, ...rels: string[]) return false; } -export function getLink(value: Resource | ResourceLinks, rel: string): ResourceLink { - return value ? (value._links ? value._links[rel] : value[rel]) : undefined; -} - export type ResourceMethod = 'GET' | 'DELETE' | 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 979196d7a..66e91660d 100644 --- a/src/Squidex/app/shared/services/app-languages.service.spec.ts +++ b/src/Squidex/app/shared/services/app-languages.service.spec.ts @@ -181,5 +181,5 @@ function createLanguage(code: string, codes: string[], i: number) { update: { method: 'PUT', href: `/languages/${code}` } }; - return new AppLanguageDto(links, code, code, i === 0, i % 2 === 1, codes.filter(x => x !== code)) + return new AppLanguageDto(links, code, code, i === 0, i % 2 === 1, codes.filter(x => x !== code)); } \ 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 97aacbab7..e0fc6aef2 100644 --- a/src/Squidex/app/shared/services/apps.service.ts +++ b/src/Squidex/app/shared/services/apps.service.ts @@ -14,6 +14,7 @@ import { AnalyticsService, ApiUrlConfig, DateTime, + hasAnyLink, pretifyError, Resource, ResourceLinks @@ -27,8 +28,8 @@ export class AppDto { public readonly canReadBackups: boolean; public readonly canReadClients: boolean; public readonly canReadContributors: boolean; - public readonly canReadPatterns: boolean; public readonly canReadLanguages: boolean; + public readonly canReadPatterns: boolean; public readonly canReadPlans: boolean; public readonly canReadRoles: boolean; public readonly canReadRules: boolean; @@ -46,6 +47,18 @@ export class AppDto { public readonly planUpgrade?: string ) { this._links = links; + + this.canCreateSchema = hasAnyLink(links, 'schemas/create'); + this.canDelete = hasAnyLink(links, 'delete'); + this.canReadBackups = hasAnyLink(links, 'backups'); + this.canReadClients = hasAnyLink(links, 'clients'); + this.canReadContributors = hasAnyLink(links, 'contributors'); + this.canReadLanguages = hasAnyLink(links, 'languages'); + this.canReadPatterns = hasAnyLink(links, 'patterns'); + this.canReadPlans = hasAnyLink(links, 'plans'); + this.canReadRoles = hasAnyLink(links, 'roles'); + this.canReadRules = hasAnyLink(links, 'rules'); + this.canReadSchemas = hasAnyLink(links, 'schemas'); } } diff --git a/src/Squidex/app/shared/services/assets.service.ts b/src/Squidex/app/shared/services/assets.service.ts index 8f7c7f284..4b9576de4 100644 --- a/src/Squidex/app/shared/services/assets.service.ts +++ b/src/Squidex/app/shared/services/assets.service.ts @@ -127,7 +127,7 @@ export class AssetsService { return this.http.get<{ total: number, items: any[] } & Resource>(url).pipe( map(({ total, items, _links }) => { - const assets = items.map(item => parseAsset(item)); ; + const assets = items.map(item => parseAsset(item)); return new AssetsDto(total, assets, _links); }), diff --git a/src/Squidex/app/shared/services/contents.service.spec.ts b/src/Squidex/app/shared/services/contents.service.spec.ts index 1c96a6c58..00b9224c3 100644 --- a/src/Squidex/app/shared/services/contents.service.spec.ts +++ b/src/Squidex/app/shared/services/contents.service.spec.ts @@ -178,13 +178,13 @@ describe('ContentsService', () => { const resource: Resource = { _links: { - ['update/change']: { method: 'PUT', href: '/api/content/my-app/my-schema/content1?asDraft=true' } + update: { method: 'PUT', href: '/api/content/my-app/my-schema/content1?asDraft=true' } } }; let content: ContentDto; - contentsService.putContent('my-app', resource, dto, true, version).subscribe(result => { + contentsService.putContent('my-app', resource, dto, version).subscribe(result => { content = result; }); @@ -225,18 +225,18 @@ describe('ContentsService', () => { expect(content!).toEqual(createContent(12)); })); - it('should make put request to discard changes', + it('should make put request to discard draft', inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { const resource: Resource = { _links: { - discard: { method: 'PUT', href: '/api/content/my-app/my-schema/content1/discard' } + ['draft/discard']: { method: 'PUT', href: '/api/content/my-app/my-schema/content1/discard' } } }; let content: ContentDto; - contentsService.discardChanges('my-app', resource, version).subscribe(result => { + contentsService.discardDraft('my-app', resource, version).subscribe(result => { content = result; }); @@ -250,6 +250,58 @@ describe('ContentsService', () => { expect(content!).toEqual(createContent(12)); })); + it('should make put request to propose draft', + inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { + + const dto = {}; + + const resource: Resource = { + _links: { + ['draft/propose']: { method: 'PUT', href: '/api/content/my-app/my-schema/content1/status' } + } + }; + + let content: ContentDto; + + contentsService.proposeDraft('my-app', resource, dto, version).subscribe(result => { + content = result; + }); + + const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/content1/status'); + + expect(req.request.method).toEqual('PUT'); + expect(req.request.headers.get('If-Match')).toBe(version.value); + + req.flush(contentResponse(12)); + + expect(content!).toEqual(createContent(12)); + })); + + it('should make put request to publish draft', + inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { + + const resource: Resource = { + _links: { + ['draft/publish']: { method: 'PUT', href: '/api/content/my-app/my-schema/content1/status' } + } + }; + + let content: ContentDto; + + contentsService.publishDraft('my-app', resource, null, version).subscribe(result => { + content = result; + }); + + const req = httpMock.expectOne('http://service/p/api/content/my-app/my-schema/content1/status'); + + expect(req.request.method).toEqual('PUT'); + expect(req.request.headers.get('If-Match')).toBe(version.value); + + req.flush(contentResponse(12)); + + expect(content!).toEqual(createContent(12)); + })); + it('should make put request to change content status', inject([ContentsService, HttpTestingController], (contentsService: ContentsService, httpMock: HttpTestingController) => { diff --git a/src/Squidex/app/shared/services/contents.service.ts b/src/Squidex/app/shared/services/contents.service.ts index 36c5a8157..f2b594b7d 100644 --- a/src/Squidex/app/shared/services/contents.service.ts +++ b/src/Squidex/app/shared/services/contents.service.ts @@ -50,9 +50,9 @@ export class ContentDto { public readonly statusUpdates: string[]; public readonly canDelete: boolean; - public readonly canDiscardChanges: boolean; - public readonly canProposeChange: boolean; - public readonly canPublishChanges: boolean; + public readonly canDraftDiscard: boolean; + public readonly canDraftPropose: boolean; + public readonly canDraftPublish: boolean; public readonly canUpdate: boolean; constructor(links: ResourceLinks, @@ -70,6 +70,12 @@ export class ContentDto { ) { this._links = links; + this.canDelete = hasAnyLink(links, 'delete'); + this.canDraftDiscard = hasAnyLink(links, 'draft/discard'); + this.canDraftPropose = hasAnyLink(links, 'draft/propose'); + this.canDraftPublish = hasAnyLink(links, 'draft/publish'); + this.canUpdate = hasAnyLink(links, 'update'); + this.statusUpdates = Object.keys(links).filter(x => x.startsWith('status/')).map(x => x.substr(7)); } } @@ -160,8 +166,8 @@ export class ContentsService { pretifyError('Failed to create content. Please reload.')); } - public putContent(appName: string, resource: Resource, dto: any, asDraft: boolean, version: Version): Observable { - const link = resource._links[asDraft ? 'update/change' : 'update']; + public putContent(appName: string, resource: Resource, dto: any, version: Version): Observable { + const link = resource._links['update']; const url = this.apiUrl.buildUrl(link.href); @@ -190,8 +196,8 @@ export class ContentsService { pretifyError('Failed to update content. Please reload.')); } - public discardChanges(appName: string, resource: Resource, version: Version): Observable { - const link = resource._links['discard']; + public discardDraft(appName: string, resource: Resource, version: Version): Observable { + const link = resource._links['draft/discard']; const url = this.apiUrl.buildUrl(link.href); @@ -202,7 +208,37 @@ export class ContentsService { tap(() => { this.analytics.trackEvent('Content', 'Discarded', appName); }), - pretifyError('Failed to discard changes. Please reload.')); + pretifyError('Failed to discard draft. Please reload.')); + } + + public proposeDraft(appName: string, resource: Resource, dto: any, version: Version): Observable { + const link = resource._links['draft/propose']; + + const url = this.apiUrl.buildUrl(link.href); + + return HTTP.putVersioned(this.http, url, dto, version).pipe( + map(({ payload }) => { + return parseContent(payload.body); + }), + tap(() => { + this.analytics.trackEvent('Content', 'Updated', appName); + }), + pretifyError('Failed to propose draft. Please reload.')); + } + + public publishDraft(appName: string, resource: Resource, dueTime: string | null, version: Version): Observable { + const link = resource._links['draft/publish']; + + const url = this.apiUrl.buildUrl(link.href); + + return HTTP.requestVersioned(this.http, link.method, url, version, { status: 'Published', dueTime }).pipe( + map(({ payload }) => { + return parseContent(payload.body); + }), + tap(() => { + this.analytics.trackEvent('Content', 'Discarded', appName); + }), + pretifyError('Failed to publish draft. Please reload.')); } public putStatus(appName: string, resource: Resource, status: string, dueTime: string | null, version: Version): Observable { @@ -248,5 +284,5 @@ function parseContent(response: any) { response.isPending === true, response.data, response.dataDraft, - new Version(response.version)); + new Version(response.version.toString())); } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/contributors.service.ts b/src/Squidex/app/shared/services/contributors.service.ts index d89360c4c..6658b98e7 100644 --- a/src/Squidex/app/shared/services/contributors.service.ts +++ b/src/Squidex/app/shared/services/contributors.service.ts @@ -114,7 +114,7 @@ function parseContributors(response: any) { item.contributorId, item.role)); - const { maxContributors, _links, _meta }= response; + const { maxContributors, _links, _meta } = response; return { items: contributors, maxContributors, _links, _meta, canCreate: hasAnyLink(_links, 'create') }; } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/rules.service.ts b/src/Squidex/app/shared/services/rules.service.ts index 9783094b9..798f73158 100644 --- a/src/Squidex/app/shared/services/rules.service.ts +++ b/src/Squidex/app/shared/services/rules.service.ts @@ -76,6 +76,14 @@ export class RuleElementPropertyDto { } export class RulesDto extends ResultSet { + public get canCreate() { + return hasAnyLink(this._links, 'create'); + } + + public get canReadEvents() { + return hasAnyLink(this._links, 'events'); + } + constructor(items: RuleDto[], links?: {}) { super(items.length, items, links); } diff --git a/src/Squidex/app/shared/state/contents.state.ts b/src/Squidex/app/shared/state/contents.state.ts index 356d051ec..a2e5b7589 100644 --- a/src/Squidex/app/shared/state/contents.state.ts +++ b/src/Squidex/app/shared/state/contents.state.ts @@ -10,7 +10,6 @@ import { forkJoin, Observable, of } from 'rxjs'; import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'; import { - DateTime, DialogService, ErrorDto, ImmutableArray, @@ -228,8 +227,8 @@ export abstract class ContentsStateBase extends State { shareSubscribed(this.dialogs, { silent: true })); } - public publishChanges(content: ContentDto, dueTime: string | null): Observable { - return this.contentsService.putStatus(this.appName, content, 'Published', dueTime, content.version).pipe( + public publishDraft(content: ContentDto, dueTime: string | null): Observable { + return this.contentsService.publishDraft(this.appName, content, dueTime, content.version).pipe( tap(updated => { this.dialogs.notifyInfo('Content updated successfully.'); @@ -248,8 +247,8 @@ export abstract class ContentsStateBase extends State { shareSubscribed(this.dialogs)); } - public update(content: ContentDto, request: any, now?: DateTime): Observable { - return this.contentsService.putContent(this.appName, content, request, false, content.version).pipe( + public update(content: ContentDto, request: any): Observable { + return this.contentsService.putContent(this.appName, content, request, content.version).pipe( tap(updated => { this.dialogs.notifyInfo('Content updated successfully.'); @@ -258,8 +257,8 @@ export abstract class ContentsStateBase extends State { shareSubscribed(this.dialogs)); } - public proposeUpdate(content: ContentDto, request: any): Observable { - return this.contentsService.putContent(this.appName, content, request, true, content.version).pipe( + public proposeDraft(content: ContentDto, request: any): Observable { + return this.contentsService.proposeDraft(this.appName, content, request, content.version).pipe( tap(updated => { this.dialogs.notifyInfo('Content updated successfully.'); @@ -268,8 +267,8 @@ export abstract class ContentsStateBase extends State { shareSubscribed(this.dialogs)); } - public discardChanges(content: ContentDto, now?: DateTime): Observable { - return this.contentsService.discardChanges(this.appName, content, content.version).pipe( + public discardDraft(content: ContentDto): Observable { + return this.contentsService.discardDraft(this.appName, content, content.version).pipe( tap(updated => { this.dialogs.notifyInfo('Content updated successfully.'); @@ -278,7 +277,7 @@ export abstract class ContentsStateBase extends State { shareSubscribed(this.dialogs)); } - public patch(content: ContentDto, request: any, now?: DateTime): Observable { + public patch(content: ContentDto, request: any): Observable { return this.contentsService.patchContent(this.appName, content, request, content.version).pipe( tap(updated => { this.dialogs.notifyInfo('Content updated successfully.'); diff --git a/src/Squidex/app/shared/state/rules.state.ts b/src/Squidex/app/shared/state/rules.state.ts index e73119f17..fecbefa44 100644 --- a/src/Squidex/app/shared/state/rules.state.ts +++ b/src/Squidex/app/shared/state/rules.state.ts @@ -29,11 +29,17 @@ interface Snapshot { // The current rules. rules: RulesList; - // The resource links. - links: ResourceLinks; - // Indicates if the rules are loaded. isLoaded?: boolean; + + // Indicates if the user can create rules. + canCreate?: boolean; + + // Indicates if the user can read events. + canReadEvents?: boolean; + + // The resource links. + _links?: ResourceLinks; } type RulesList = ImmutableArray; @@ -49,7 +55,11 @@ export class RulesState extends State { distinctUntilChanged()); public canCreate = - this.changes.pipe(map(x => x.links), + this.changes.pipe(map(x => !!x.canCreate), + distinctUntilChanged()); + + public canReadEvents = + this.changes.pipe(map(x => !!x.canReadEvents), distinctUntilChanged()); constructor( @@ -57,7 +67,7 @@ export class RulesState extends State { private readonly dialogs: DialogService, private readonly rulesService: RulesService ) { - super({ rules: ImmutableArray.empty(), links: {} }); + super({ rules: ImmutableArray.empty() }); } public load(isReload = false): Observable { @@ -66,7 +76,7 @@ export class RulesState extends State { } return this.rulesService.getRules(this.appName).pipe( - tap(({ items, _links: links }) => { + tap(({ items, _links, canCreate, canReadEvents }) => { if (isReload) { this.dialogs.notifyInfo('Rules reloaded.'); } @@ -74,7 +84,7 @@ export class RulesState extends State { this.next(s => { const rules = ImmutableArray.of(items); - return { ...s, rules, isLoaded: true, links }; + return { ...s, rules, isLoaded: true, _links, canCreate, canReadEvents }; }); }), shareSubscribed(this.dialogs)); diff --git a/src/Squidex/app/shared/state/ui.state.spec.ts b/src/Squidex/app/shared/state/ui.state.spec.ts index 71ab2f003..0e7e75de3 100644 --- a/src/Squidex/app/shared/state/ui.state.spec.ts +++ b/src/Squidex/app/shared/state/ui.state.spec.ts @@ -39,7 +39,7 @@ describe('UIState', () => { const resources: ResourceLinks = { ['admin/events']: { method: 'GET', href: '/api/events' }, ['admin/restore']: { method: 'GET', href: '/api/restore' }, - ['admin/users']: { method: 'GET', href: '/api/users' }, + ['admin/users']: { method: 'GET', href: '/api/users' } }; let usersService: IMock;