From e4f9f98f2d4195796317d35182c12a636557ffc0 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 22 Jan 2024 08:20:45 +0100 Subject: [PATCH] File system endpoints redo (#15521) * First stab at a massive remake of file system based endpoints * Do not prefix system paths with directory separator char * Ensure correct and consistent response types * Fix partial view snippets endpoints * Clean up IO (path) operations * Update OpenAPI JSON to match new endpoints * Return 201 when renaming file system resources * Add "IsFolder" to file system item endpoints * Replace "parentPath" with a "parent" object for file system creation endpoints * Update OpenAPI JSON * Rewrite snippets * Regenerate OpenAPI JSON after forward merge * Remove stylesheet overview endpoint * Regenerate OpenAPI JSON after forward merge * add server-file-system module to importmap * Expose generated resource identifier in 201 responses --------- Co-authored-by: Mads Rasmussen --- .../EmptyCreatedAtActionResult.cs | 12 +- .../DataType/CopyDataTypeController.cs | 2 +- .../DataType/CreateDataTypeController.cs | 2 +- .../Dictionary/CreateDictionaryController.cs | 2 +- .../Dictionary/ImportDictionaryController.cs | 2 +- .../Document/CopyDocumentController.cs | 2 +- .../Document/CreateDocumentController.cs | 2 +- .../CreatePublicAccessDocumentController.cs | 2 +- .../CopyDocumentTypeController.cs | 2 +- .../CreateDocumentTypeController.cs | 2 +- .../FileSystemManagementControllerBase.cs | 21 + .../FolderManagementControllerBase.cs | 2 +- .../Language/CreateLanguageController.cs | 2 +- .../CreateSavedSearchLogViewerController.cs | 2 +- .../ManagementApiControllerBase.cs | 24 +- .../Media/CreateMediaController.cs | 2 +- .../MediaType/CopyMediaTypeController.cs | 2 +- .../MediaType/CreateMediaTypeController.cs | 2 +- .../Created/CreateCreatedPackageController.cs | 2 +- .../ByPathPartialViewController.cs | 11 +- .../CreatePartialViewController.cs | 16 +- .../DeletePartialViewController.cs | 6 +- .../ByPathPartialViewFolderController.cs | 27 +- .../CreatePartialViewFolderController.cs | 24 +- .../DeletePartialViewFolderController.cs | 26 +- .../Folder/PartialViewFolderControllerBase.cs | 26 +- .../ItemPartialViewItemController.cs | 19 +- .../PartialViewItemControllerBase.cs | 2 +- .../PartialView/PartialViewControllerBase.cs | 2 +- .../RenamePartialViewController.cs | 45 + ...{ByNameController.cs => ByIdController.cs} | 22 +- .../PartialView/Snippet/GetAllController.cs | 17 +- .../Tree/ChildrenPartialViewTreeController.cs | 4 +- .../UpdatePartialViewController.cs | 11 +- .../PathFolderManagementControllerBase.cs | 56 - .../Query/CreateRelationTypeController.cs | 2 +- .../Script/ByPathScriptController.cs | 11 +- .../Script/CreateScriptController.cs | 18 +- .../Script/DeleteScriptController.cs | 6 +- .../Folder/ByPathScriptFolderController.cs | 30 +- .../Folder/CreateScriptFolderController.cs | 24 +- .../Folder/DeleteScriptFolderController.cs | 24 +- .../Folder/ScriptFolderControllerBase.cs | 26 +- .../Script/Item/ItemScriptItemController.cs | 15 +- .../Script/RenameScriptController.cs | 45 + .../Script/ScriptControllerBase.cs | 2 +- .../Tree/ChildrenScriptTreeController.cs | 4 +- .../Script/UpdateScriptController.cs | 21 +- .../Item/ItemStaticFileItemController.cs | 17 +- .../Tree/ChildrenStaticFileTreeController.cs | 4 +- .../Stylesheet/AllStylesheetController.cs | 37 - .../Stylesheet/ByPathStylesheetController.cs | 16 +- .../Stylesheet/CreateStylesheetController.cs | 6 +- .../Stylesheet/DeleteStylesheetController.cs | 6 +- .../ExtractRichTextRulesController.cs | 40 - .../ByPathStylesheetFolderController.cs | 27 +- .../CreateStylesheetFolderController.cs | 23 +- .../DeleteStylesheetFolderController.cs | 24 +- .../Folder/StylesheetFolderControllerBase.cs | 25 +- .../GetRichTextRulesByPathController.cs | 44 - .../InterpolateRichTextRulesController.cs | 39 - .../Item/ItemStylesheetItemController.cs | 18 +- .../Stylesheet/RenameStylesheetController.cs | 45 + .../Stylesheet/StylesheetControllerBase.cs | 2 +- .../Tree/ChildrenStylesheetTreeController.cs | 4 +- .../Stylesheet/UpdateStylesheetController.cs | 10 +- .../Template/CreateTemplateController.cs | 2 +- .../CreateTemporaryFileController.cs | 2 +- .../Tree/FileSystemTreeControllerBase.cs | 32 +- .../Controllers/User/InviteUsersController.cs | 2 +- .../UserGroup/CreateUserGroupController.cs | 2 +- .../BackOfficeCorsPolicyBuilderExtensions.cs | 1 + .../PathFolderBuilderExtensions.cs | 16 - .../UmbracoBuilderExtensions.cs | 1 - .../Extensions/StringPathExtensions.cs | 10 + .../FileItemPresentationModelFactory.cs | 74 +- .../IFileItemPresentationModelFactory.cs | 10 +- .../PathFolderViewModelMapDefinition.cs | 28 - .../PartialViewViewModelsMapDefinition.cs | 71 +- .../Script/ScriptViewModelsMapDefinition.cs | 61 +- .../StylesheetviewModelsMapDefinition.cs | 106 +- src/Umbraco.Cms.Api.Management/OpenApi.json | 3470 ++++++++++------- .../OpenApi/ReponseHeaderOperationFilter.cs | 7 +- .../FileSystemCreateRequestModelBase.cs | 8 + .../FileSystemFileCreateRequestModelBase.cs | 6 + .../FileSystemFileResponseModelBase.cs | 6 + .../FileSystemFileUpdateRequestModelBase.cs | 6 + .../FileSystem/FileSystemFolderModel.cs | 5 + .../FileSystemItemResponseModelBase.cs | 6 + .../FileSystem/FileSystemItemViewModelBase.cs | 6 + .../FileSystemRenameRequestModelBase.cs | 6 + .../FileSystem/FileSystemResponseModelBase.cs | 8 + .../Folder/CreatePathFolderRequestModel.cs | 6 - .../ViewModels/Folder/PathFolderModelBase.cs | 5 - .../Folder/PathFolderResponseModel.cs | 11 - .../Item/FileItemResponseModelBase.cs | 10 - .../Item/SnippetItemResponseModel.cs | 6 - .../CreatePartialViewRequestModel.cs | 4 +- .../CreatePartialViewFolderRequestModel.cs | 7 + .../Folder/PartialViewFolderResponseModel.cs | 7 + .../Item/PartialViewItemResponseModel.cs | 4 +- .../PartialView/PartialViewResponseModel.cs | 4 +- .../RenamePartialViewRequestModel.cs | 7 + .../PartialViewSnippetItemResponseModel.cs | 8 + ....cs => PartialViewSnippetResponseModel.cs} | 4 +- .../Snippets/SnippetItemResponseModel.cs | 19 - .../UpdatePartialViewRequestModel.cs | 4 +- ...ractRichTextStylesheetRulesRequestModel.cs | 6 - ...actRichTextStylesheetRulesResponseModel.cs | 6 - ...terpolateRichTextStylesheetRequestModel.cs | 8 - ...erpolateRichTextStylesheetResponseModel.cs | 6 - .../RichTextRuleViewModel.cs | 10 - .../RichTextStylesheetRulesResponseModel.cs | 6 - .../Script/CreateScriptRequestModel.cs | 4 +- .../Folder/CreateScriptFolderRequestModel.cs | 7 + .../Folder/ScriptFolderResponseModel.cs | 7 + .../Script/Item/ScriptItemResponseModel.cs | 4 +- .../Script/RenameScriptRequestModel.cs | 7 + .../ViewModels/Script/ScriptResponseModel.cs | 4 +- .../ViewModels/Script/ScriptViewModelBase.cs | 7 - .../Script/UpdateScriptRequestModel.cs | 4 +- .../Item/StaticFileItemResponseModel.cs | 4 +- .../CreateStylesheetRequestModel.cs | 4 +- .../CreateStylesheetFolderRequestModel.cs | 7 + .../Folder/StylesheetFolderResponseModel.cs | 7 + .../Item/StylesheetItemResponseModel.cs | 4 +- .../RenameStylesheetRequestModel.cs | 7 + .../StylesheetOverviewResponseModel.cs | 8 - .../Stylesheet/StylesheetResponseModel.cs | 4 +- .../UpdateStylesheetRequestModel.cs | 4 +- .../TextFiles/CreateTextFileViewModelBase.cs | 6 - .../TextFiles/TextFileResponseModelBase.cs | 9 - .../TextFiles/TextFileViewModelBase.cs | 8 - .../TextFiles/UpdateTextFileViewModelBase.cs | 7 - .../FileSystemTreeItemPresentationModel.cs | 6 +- .../umbraco/UmbracoBackOffice/Default.cshtml | 1 + .../umbraco/UmbracoInstall/Index.cshtml | 1 + src/Umbraco.Core/Constants-Headers.cs | 17 + .../UmbracoBuilder.Collections.cs | 8 - .../DependencyInjection/UmbracoBuilder.cs | 2 +- .../Models/FileSystem/FolderCreateModel.cs | 8 + .../Models/FileSystem/FolderModelBase.cs | 10 + .../PartialViewFolderCreateModel.cs | 5 + .../FileSystem/PartialViewFolderModel.cs | 5 + .../FileSystem/ScriptFolderCreateModel.cs | 5 + .../Models/FileSystem/ScriptFolderModel.cs | 5 + .../FileSystem/StylesheetFolderCreateModel.cs | 5 + .../FileSystem/StylesheetFolderModel.cs | 5 + .../Models/PartialViewRenameModel.cs | 5 + src/Umbraco.Core/Models/PathContainer.cs | 25 - src/Umbraco.Core/Models/ScriptRenameModel.cs | 5 + .../Models/StylesheetRenameModel.cs | 5 + .../Models/TextFileCreateModel.cs | 7 +- .../Models/TextFileRenameModel.cs | 9 + .../Models/TextFileUpdateModel.cs | 12 +- .../Services/FileServiceOperationBase.cs | 258 ++ .../FileSystem/FolderServiceOperationBase.cs | 140 + .../FileSystem/IPartialViewFolderService.cs | 13 + .../FileSystem/IScriptFolderService.cs | 13 + .../FileSystem/IStylesheetFolderService.cs | 13 + .../FileSystem/PartialViewFolderService.cs | 35 + .../FileSystem/ScriptFolderService.cs | 35 + .../FileSystem/StylesheetFolderService.cs | 35 + .../Services/IPartialViewFolderService.cs | 8 - .../Services/IPartialViewService.cs | 24 +- .../Services/IPathFolderService.cs | 12 - .../Services/IScriptFolderService.cs | 8 - src/Umbraco.Core/Services/IScriptService.cs | 12 +- .../Services/IStylesheetFolderService.cs | 8 - .../Services/IStylesheetService.cs | 12 +- .../Services/PartialViewFolderService.cs | 64 - .../Services/PartialViewService.cs | 263 +- .../Services/PathFolderServiceBase.cs | 90 - .../Services/RichTextStylesheetService.cs | 73 - .../Services/ScriptFolderService.cs | 64 - src/Umbraco.Core/Services/ScriptService.cs | 195 +- .../Services/StylesheetFolderService.cs | 62 - .../Services/StylesheetService.cs | 193 +- src/Umbraco.Core/Snippets/ISnippet.cs | 18 - .../PartialViewMacroSnippetCollection.cs | 66 - ...artialViewMacroSnippetCollectionBuilder.cs | 56 - .../Snippets/PartialViewSnippet.cs | 16 +- .../Snippets/PartialViewSnippetCollection.cs | 71 +- .../PartialViewSnippetCollectionBuilder.cs | 83 +- .../Snippets/PartialViewSnippetSlim.cs | 23 + src/Umbraco.Core/Snippets/Snippet.cs | 15 - .../Strings/Css/StylesheetHelper.cs | 1 + .../Controllers/CodeFileController.cs | 61 +- 188 files changed, 3991 insertions(+), 3441 deletions(-) create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/FileSystemManagementControllerBase.cs rename src/Umbraco.Cms.Api.Management/Controllers/PartialView/{Items => Item}/ItemPartialViewItemController.cs (55%) rename src/Umbraco.Cms.Api.Management/Controllers/PartialView/{Items => Item}/PartialViewItemControllerBase.cs (99%) create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/PartialView/RenamePartialViewController.cs rename src/Umbraco.Cms.Api.Management/Controllers/PartialView/Snippet/{ByNameController.cs => ByIdController.cs} (62%) delete mode 100644 src/Umbraco.Cms.Api.Management/Controllers/PathFolderManagementControllerBase.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Script/RenameScriptController.cs delete mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/AllStylesheetController.cs delete mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ExtractRichTextRulesController.cs delete mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/GetRichTextRulesByPathController.cs delete mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/InterpolateRichTextRulesController.cs create mode 100644 src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/RenameStylesheetController.cs delete mode 100644 src/Umbraco.Cms.Api.Management/DependencyInjection/PathFolderBuilderExtensions.cs create mode 100644 src/Umbraco.Cms.Api.Management/Extensions/StringPathExtensions.cs delete mode 100644 src/Umbraco.Cms.Api.Management/Mapping/Folder/PathFolderViewModelMapDefinition.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemCreateRequestModelBase.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileCreateRequestModelBase.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileResponseModelBase.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileUpdateRequestModelBase.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFolderModel.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemItemResponseModelBase.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemItemViewModelBase.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemRenameRequestModelBase.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemResponseModelBase.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Folder/CreatePathFolderRequestModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Folder/PathFolderModelBase.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Folder/PathFolderResponseModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Item/FileItemResponseModelBase.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Item/SnippetItemResponseModel.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Folder/CreatePartialViewFolderRequestModel.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Folder/PartialViewFolderResponseModel.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/PartialView/RenamePartialViewRequestModel.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/PartialViewSnippetItemResponseModel.cs rename src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/{PartialViewSnippetsViewModel.cs => PartialViewSnippetResponseModel.cs} (57%) delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/SnippetItemResponseModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/ExtractRichTextStylesheetRulesRequestModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/ExtractRichTextStylesheetRulesResponseModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/InterpolateRichTextStylesheetRequestModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/InterpolateRichTextStylesheetResponseModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/RichTextRuleViewModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/RichTextStylesheetRulesResponseModel.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Script/Folder/CreateScriptFolderRequestModel.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Script/Folder/ScriptFolderResponseModel.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Script/RenameScriptRequestModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Script/ScriptViewModelBase.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Folder/CreateStylesheetFolderRequestModel.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Folder/StylesheetFolderResponseModel.cs create mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/RenameStylesheetRequestModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/StylesheetOverviewResponseModel.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/CreateTextFileViewModelBase.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/TextFileResponseModelBase.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/TextFileViewModelBase.cs delete mode 100644 src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/UpdateTextFileViewModelBase.cs create mode 100644 src/Umbraco.Core/Constants-Headers.cs create mode 100644 src/Umbraco.Core/Models/FileSystem/FolderCreateModel.cs create mode 100644 src/Umbraco.Core/Models/FileSystem/FolderModelBase.cs create mode 100644 src/Umbraco.Core/Models/FileSystem/PartialViewFolderCreateModel.cs create mode 100644 src/Umbraco.Core/Models/FileSystem/PartialViewFolderModel.cs create mode 100644 src/Umbraco.Core/Models/FileSystem/ScriptFolderCreateModel.cs create mode 100644 src/Umbraco.Core/Models/FileSystem/ScriptFolderModel.cs create mode 100644 src/Umbraco.Core/Models/FileSystem/StylesheetFolderCreateModel.cs create mode 100644 src/Umbraco.Core/Models/FileSystem/StylesheetFolderModel.cs create mode 100644 src/Umbraco.Core/Models/PartialViewRenameModel.cs delete mode 100644 src/Umbraco.Core/Models/PathContainer.cs create mode 100644 src/Umbraco.Core/Models/ScriptRenameModel.cs create mode 100644 src/Umbraco.Core/Models/StylesheetRenameModel.cs create mode 100644 src/Umbraco.Core/Models/TextFileRenameModel.cs create mode 100644 src/Umbraco.Core/Services/FileServiceOperationBase.cs create mode 100644 src/Umbraco.Core/Services/FileSystem/FolderServiceOperationBase.cs create mode 100644 src/Umbraco.Core/Services/FileSystem/IPartialViewFolderService.cs create mode 100644 src/Umbraco.Core/Services/FileSystem/IScriptFolderService.cs create mode 100644 src/Umbraco.Core/Services/FileSystem/IStylesheetFolderService.cs create mode 100644 src/Umbraco.Core/Services/FileSystem/PartialViewFolderService.cs create mode 100644 src/Umbraco.Core/Services/FileSystem/ScriptFolderService.cs create mode 100644 src/Umbraco.Core/Services/FileSystem/StylesheetFolderService.cs delete mode 100644 src/Umbraco.Core/Services/IPartialViewFolderService.cs delete mode 100644 src/Umbraco.Core/Services/IPathFolderService.cs delete mode 100644 src/Umbraco.Core/Services/IScriptFolderService.cs delete mode 100644 src/Umbraco.Core/Services/IStylesheetFolderService.cs delete mode 100644 src/Umbraco.Core/Services/PartialViewFolderService.cs delete mode 100644 src/Umbraco.Core/Services/PathFolderServiceBase.cs delete mode 100644 src/Umbraco.Core/Services/RichTextStylesheetService.cs delete mode 100644 src/Umbraco.Core/Services/ScriptFolderService.cs delete mode 100644 src/Umbraco.Core/Services/StylesheetFolderService.cs delete mode 100644 src/Umbraco.Core/Snippets/ISnippet.cs delete mode 100644 src/Umbraco.Core/Snippets/PartialViewMacroSnippetCollection.cs delete mode 100644 src/Umbraco.Core/Snippets/PartialViewMacroSnippetCollectionBuilder.cs create mode 100644 src/Umbraco.Core/Snippets/PartialViewSnippetSlim.cs delete mode 100644 src/Umbraco.Core/Snippets/Snippet.cs diff --git a/src/Umbraco.Cms.Api.Common/Mvc/ActionResults/EmptyCreatedAtActionResult.cs b/src/Umbraco.Cms.Api.Common/Mvc/ActionResults/EmptyCreatedAtActionResult.cs index 0a50a87ccf..03b4ebfcc3 100644 --- a/src/Umbraco.Cms.Api.Common/Mvc/ActionResults/EmptyCreatedAtActionResult.cs +++ b/src/Umbraco.Cms.Api.Common/Mvc/ActionResults/EmptyCreatedAtActionResult.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core; namespace Umbraco.Cms.Api.Common.Mvc.ActionResults; @@ -10,15 +11,17 @@ namespace Umbraco.Cms.Api.Common.Mvc.ActionResults; /// public sealed class EmptyCreatedAtActionResult : ActionResult { - private readonly string? _actionName; - private readonly string? _controllerName; - private readonly object? _routeValues; + private readonly string _actionName; + private readonly string _controllerName; + private readonly object _routeValues; + private readonly string _resourceIdentifier; - public EmptyCreatedAtActionResult(string? actionName, string? controllerName, object? routeValues) + public EmptyCreatedAtActionResult(string actionName, string controllerName, object routeValues, string resourceIdentifier) { _actionName = actionName; _controllerName = controllerName; _routeValues = routeValues; + _resourceIdentifier = resourceIdentifier; } public override void ExecuteResult(ActionContext context) @@ -42,5 +45,6 @@ public sealed class EmptyCreatedAtActionResult : ActionResult context.HttpContext.Response.StatusCode = StatusCodes.Status201Created; context.HttpContext.Response.Headers.Location = url; + context.HttpContext.Response.Headers[Constants.Headers.GeneratedResource] = _resourceIdentifier; } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs index a107c5138f..ed6c22aa07 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/CopyDataTypeController.cs @@ -37,7 +37,7 @@ public class CopyDataTypeController : DataTypeControllerBase Attempt result = await _dataTypeService.CopyAsync(source, copyDataTypeRequestModel.TargetId, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result.Key) : DataTypeOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/CreateDataTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/CreateDataTypeController.cs index 7658fa64b4..b835acc305 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/CreateDataTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/CreateDataTypeController.cs @@ -41,7 +41,7 @@ public class CreateDataTypeController : DataTypeControllerBase Attempt result = await _dataTypeService.CreateAsync(attempt.Result, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result.Key) : DataTypeOperationStatusResult(result.Status); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs index 7a372b8748..7bd27b6dfa 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/CreateDictionaryController.cs @@ -42,7 +42,7 @@ public class CreateDictionaryController : DictionaryControllerBase await _dictionaryItemService.CreateAsync(created, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result!.Key) : DictionaryItemOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ImportDictionaryController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ImportDictionaryController.cs index 463a431929..6305628663 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ImportDictionaryController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/ImportDictionaryController.cs @@ -40,7 +40,7 @@ public class ImportDictionaryController : DictionaryControllerBase return result.Status switch { - DictionaryImportOperationStatus.Success => CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key), + DictionaryImportOperationStatus.Success => CreatedAtId(controller => nameof(controller.ByKey), result.Result!.Key), DictionaryImportOperationStatus.ParentNotFound => NotFound(new ProblemDetailsBuilder() .WithTitle("The parent dictionary item could not be found.") .Build()), diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/CopyDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/CopyDocumentController.cs index f331c912e5..00e3823cea 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/CopyDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/CopyDocumentController.cs @@ -56,7 +56,7 @@ public class CopyDocumentController : DocumentControllerBase CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result!.Key) : ContentEditingOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentController.cs index e06c9f9736..4ce888b74c 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/CreateDocumentController.cs @@ -57,7 +57,7 @@ public class CreateDocumentController : DocumentControllerBase Attempt result = await _contentEditingService.CreateAsync(model, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result!.Key) : ContentEditingOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/CreatePublicAccessDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/CreatePublicAccessDocumentController.cs index f18d93e06b..5901ea9ed5 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Document/CreatePublicAccessDocumentController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/CreatePublicAccessDocumentController.cs @@ -53,7 +53,7 @@ public class CreatePublicAccessDocumentController : DocumentControllerBase Attempt saveAttempt = await _publicAccessService.CreateAsync(publicAccessEntrySlim); return saveAttempt.Success - ? CreatedAtAction(controller => nameof(controller.GetPublicAccess), saveAttempt.Result!.Key) + ? CreatedAtId(controller => nameof(controller.GetPublicAccess), saveAttempt.Result!.Key) : PublicAccessOperationStatusResult(saveAttempt.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/CopyDocumentTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/CopyDocumentTypeController.cs index 94ff0c72d3..cea14961da 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/CopyDocumentTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/CopyDocumentTypeController.cs @@ -27,7 +27,7 @@ public class CopyDocumentTypeController : DocumentTypeControllerBase Attempt result = await _contentTypeService.CopyAsync(id, copyDocumentTypeRequestModel.TargetId); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result!.Key) : StructureOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/CreateDocumentTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/CreateDocumentTypeController.cs index 2126776e4f..237d129d58 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/CreateDocumentTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/CreateDocumentTypeController.cs @@ -40,7 +40,7 @@ public class CreateDocumentTypeController : DocumentTypeControllerBase Attempt result = await _contentTypeEditingService.CreateAsync(model, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result!.Key) : OperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/FileSystemManagementControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/FileSystemManagementControllerBase.cs new file mode 100644 index 0000000000..c17dbe1f25 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/FileSystemManagementControllerBase.cs @@ -0,0 +1,21 @@ +using System.Net; + +namespace Umbraco.Cms.Api.Management.Controllers; + +public abstract class FileSystemManagementControllerBase : ManagementApiControllerBase +{ + protected string DecodePath(string path) + { + // OpenAPI does not allow reserved chars as "in:path" parameters, so clients based on the Swagger JSON will URL + // encode the path. Normally, ASP.NET Core handles that encoding with an automatic decoding - apparently just not + // for forward slashes, for whatever reason... so we need to deal with those. Hopefully this will be addressed in + // an upcoming version of ASP.NET Core. + // See also https://github.com/dotnet/aspnetcore/issues/11544 + if (path.Contains("%2F", StringComparison.OrdinalIgnoreCase)) + { + path = WebUtility.UrlDecode(path); + } + + return path; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/FolderManagementControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/FolderManagementControllerBase.cs index 6d97b358a7..9c6c9da084 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/FolderManagementControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/FolderManagementControllerBase.cs @@ -56,7 +56,7 @@ public abstract class FolderManagementControllerBase : ManagementAp CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(createdAction, result.Result!.Key) + ? CreatedAtId(createdAction, result.Result!.Key) : OperationStatusResult(result.Status); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Language/CreateLanguageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Language/CreateLanguageController.cs index fb7cb7d9d2..680e6543a7 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Language/CreateLanguageController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Language/CreateLanguageController.cs @@ -43,7 +43,7 @@ public class CreateLanguageController : LanguageControllerBase Attempt result = await _languageService.CreateAsync(created, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByIsoCode), new { isoCode = result.Result.IsoCode }) + ? CreatedAtAction(controller => nameof(controller.ByIsoCode), new { isoCode = result.Result.IsoCode }, result.Result.IsoCode) : LanguageOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/SavedSearch/CreateSavedSearchLogViewerController.cs b/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/SavedSearch/CreateSavedSearchLogViewerController.cs index e291c383ae..cc157ec266 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/SavedSearch/CreateSavedSearchLogViewerController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/SavedSearch/CreateSavedSearchLogViewerController.cs @@ -33,7 +33,7 @@ public class CreateSavedSearchLogViewerController : SavedSearchLogViewerControll if (result.Success) { return CreatedAtAction( - controller => nameof(controller.ByName), savedSearch.Name); + controller => nameof(controller.ByName), new { name = savedSearch.Name }, savedSearch.Name); } return LogViewerOperationStatusResult(result.Status); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs index 40773d3bbe..e6636b34cc 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/ManagementApiControllerBase.cs @@ -19,10 +19,13 @@ namespace Umbraco.Cms.Api.Management.Controllers; [JsonOptionsName(Constants.JsonOptionsNames.BackOffice)] public abstract class ManagementApiControllerBase : Controller, IUmbracoFeature { - protected IActionResult CreatedAtAction(Expression> action, Guid id) - => CreatedAtAction(action, new { id = id }); + protected IActionResult CreatedAtId(Expression> action, Guid id) + => CreatedAtAction(action, new { id = id }, id.ToString()); - protected IActionResult CreatedAtAction(Expression> action, object routeValues) + protected IActionResult CreatedAtPath(Expression> action, string path) + => CreatedAtAction(action, new { path = path }, path); + + protected IActionResult CreatedAtAction(Expression> action, object routeValues, string resourceIdentifier) { if (action.Body is not ConstantExpression constantExpression) { @@ -32,20 +35,7 @@ public abstract class ManagementApiControllerBase : Controller, IUmbracoFeature var controllerName = ManagementApiRegexes.ControllerTypeToNameRegex().Replace(typeof(T).Name, string.Empty); var actionName = constantExpression.Value?.ToString() ?? throw new ArgumentException("Expression does not have a value."); - return new EmptyCreatedAtActionResult(actionName, controllerName, routeValues); - } - - protected IActionResult CreatedAtAction(Expression> action, string name) - { - if (action.Body is not ConstantExpression constantExpression) - { - throw new ArgumentException("Expression must be a constant expression."); - } - - var controllerName = ManagementApiRegexes.ControllerTypeToNameRegex().Replace(typeof(T).Name, string.Empty); - var actionName = constantExpression.Value?.ToString() ?? throw new ArgumentException("Expression does not have a value."); - - return new EmptyCreatedAtActionResult(actionName, controllerName, new { name = name }); + return new EmptyCreatedAtActionResult(actionName, controllerName, routeValues, resourceIdentifier); } protected static Guid CurrentUserKey(IBackOfficeSecurityAccessor backOfficeSecurityAccessor) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/CreateMediaController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/CreateMediaController.cs index 5f69fac11a..2eaef8a8e5 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Media/CreateMediaController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/CreateMediaController.cs @@ -57,7 +57,7 @@ public class CreateMediaController : MediaControllerBase Attempt result = await _mediaEditingService.CreateAsync(model, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result!.Key) : ContentEditingOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/CopyMediaTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/CopyMediaTypeController.cs index a2847a1c28..08d1587c88 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/CopyMediaTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/CopyMediaTypeController.cs @@ -27,7 +27,7 @@ public class CopyMediaTypeController : MediaTypeControllerBase Attempt result = await _mediaTypeService.CopyAsync(id, copyMediaTypeRequestModel.TargetId); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result!.Key) : StructureOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/CreateMediaTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/CreateMediaTypeController.cs index a19f8a51da..393df73f04 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/CreateMediaTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/CreateMediaTypeController.cs @@ -40,7 +40,7 @@ public class CreateMediaTypeController : MediaTypeControllerBase Attempt result = await _mediaTypeEditingService.CreateAsync(model, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result!.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result!.Key) : OperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/CreateCreatedPackageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/CreateCreatedPackageController.cs index 2e09ddbcae..087cf0ea88 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/CreateCreatedPackageController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/CreateCreatedPackageController.cs @@ -45,7 +45,7 @@ public class CreateCreatedPackageController : CreatedPackageControllerBase Attempt result = await _packagingService.CreateCreatedPackageAsync(packageDefinition, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), packageDefinition.PackageId) + ? CreatedAtId(controller => nameof(controller.ByKey), packageDefinition.PackageId) : PackageOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/ByPathPartialViewController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/ByPathPartialViewController.cs index 8e7476010b..4736ec1203 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/ByPathPartialViewController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/ByPathPartialViewController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.ViewModels.PartialView; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; @@ -22,15 +23,17 @@ public class ByPathPartialViewController : PartialViewControllerBase _mapper = mapper; } - [HttpGet] + [HttpGet("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PartialViewResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task ByPath(string path) { + path = DecodePath(path).VirtualPathToSystemPath(); IPartialView? partialView = await _partialViewService.GetAsync(path); - return partialView is null - ? PartialViewNotFound() - : Ok(_mapper.Map(partialView)); + return partialView is not null + ? Ok(_mapper.Map(partialView)) + : PartialViewNotFound(); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/CreatePartialViewController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/CreatePartialViewController.cs index ac5267b8f3..b14f1a4846 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/CreatePartialViewController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/CreatePartialViewController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.ViewModels.PartialView; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; @@ -14,31 +15,32 @@ namespace Umbraco.Cms.Api.Management.Controllers.PartialView; [ApiVersion("1.0")] public class CreatePartialViewController : PartialViewControllerBase { - private readonly IUmbracoMapper _umbracoMapper; private readonly IPartialViewService _partialViewService; + private readonly IUmbracoMapper _umbracoMapper; private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; public CreatePartialViewController( - IUmbracoMapper umbracoMapper, IPartialViewService partialViewService, + IUmbracoMapper umbracoMapper, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { - _umbracoMapper = umbracoMapper; _partialViewService = partialViewService; + _umbracoMapper = umbracoMapper; _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } [HttpPost] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status201Created)] - public async Task Create(CreatePartialViewRequestModel createRequestModel) + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Create(CreatePartialViewRequestModel requestModel) { - PartialViewCreateModel createModel = _umbracoMapper.Map(createRequestModel)!; - + PartialViewCreateModel createModel = _umbracoMapper.Map(requestModel)!; Attempt createAttempt = await _partialViewService.CreateAsync(createModel, CurrentUserKey(_backOfficeSecurityAccessor)); return createAttempt.Success - ? CreatedAtAction(controller => nameof(controller.ByPath), new { path = createAttempt.Result!.Path }) + ? CreatedAtPath(controller => nameof(controller.ByPath), createAttempt.Result!.Path.SystemPathToVirtualPath()) : PartialViewOperationStatusResult(createAttempt.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/DeletePartialViewController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/DeletePartialViewController.cs index 96c194137b..54636d3b3e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/DeletePartialViewController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/DeletePartialViewController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; @@ -21,11 +22,14 @@ public class DeletePartialViewController : PartialViewControllerBase _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - [HttpDelete] + [HttpDelete("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Delete(string path) { + path = DecodePath(path).VirtualPathToSystemPath(); PartialViewOperationStatus operationStatus = await _partialViewService.DeleteAsync(path, CurrentUserKey(_backOfficeSecurityAccessor)); return operationStatus is PartialViewOperationStatus.Success diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/ByPathPartialViewFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/ByPathPartialViewFolderController.cs index d3271158b3..ce9c70fc56 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/ByPathPartialViewFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/ByPathPartialViewFolderController.cs @@ -1,20 +1,37 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.PartialView.Folder; using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Services.FileSystem; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Folder; [ApiVersion("1.0")] public class ByPathPartialViewFolderController : PartialViewFolderControllerBase { - public ByPathPartialViewFolderController(IUmbracoMapper mapper, IPartialViewFolderService partialViewFolderService) : base(mapper, partialViewFolderService) + private readonly IPartialViewFolderService _partialViewFolderService; + private readonly IUmbracoMapper _mapper; + + public ByPathPartialViewFolderController(IPartialViewFolderService partialViewFolderService, IUmbracoMapper mapper) { + _partialViewFolderService = partialViewFolderService; + _mapper = mapper; } - [HttpGet] + [HttpGet("{path}")] [MapToApiVersion("1.0")] - [ProducesResponseType(StatusCodes.Status200OK)] - public Task ByPath(string path) => GetFolderAsync(path); + [ProducesResponseType(typeof(PartialViewFolderResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task ByPath(string path) + { + path = DecodePath(path).VirtualPathToSystemPath(); + PartialViewFolderModel? folder = await _partialViewFolderService.GetAsync(path); + return folder is not null + ? Ok(_mapper.Map(folder)) + : OperationStatusResult(PartialViewFolderOperationStatus.NotFound); + } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/CreatePartialViewFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/CreatePartialViewFolderController.cs index c8f6a2d43b..97b6b0eb24 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/CreatePartialViewFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/CreatePartialViewFolderController.cs @@ -1,11 +1,12 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Management.ViewModels.Folder; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.PartialView.Folder; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Services.FileSystem; using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Folder; @@ -13,21 +14,28 @@ namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Folder; [ApiVersion("1.0")] public class CreatePartialViewFolderController : PartialViewFolderControllerBase { - public CreatePartialViewFolderController(IUmbracoMapper mapper, IPartialViewFolderService partialViewFolderService) - : base(mapper, partialViewFolderService) + private readonly IPartialViewFolderService _partialViewFolderService; + private readonly IUmbracoMapper _mapper; + + public CreatePartialViewFolderController(IPartialViewFolderService partialViewFolderService, IUmbracoMapper mapper) { + _partialViewFolderService = partialViewFolderService; + _mapper = mapper; } + [HttpPost] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public async Task Create(CreatePathFolderRequestModel model) + public async Task Create(CreatePartialViewFolderRequestModel requestModel) { - Attempt result = await CreateAsync(model); + PartialViewFolderCreateModel createModel = _mapper.Map(requestModel)!; + Attempt result = await _partialViewFolderService.CreateAsync(createModel); + return result.Success - ? CreatedAtAction(controller => nameof(controller.ByPath), new { path = result.Result!.Path }) + ? CreatedAtPath(controller => nameof(controller.ByPath), result.Result!.Path.SystemPathToVirtualPath()) : OperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/DeletePartialViewFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/DeletePartialViewFolderController.cs index dbe76cfbdc..8fb2c3356d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/DeletePartialViewFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/DeletePartialViewFolderController.cs @@ -1,25 +1,31 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Core.Services.FileSystem; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Folder; [ApiVersion("1.0")] public class DeletePartialViewFolderController : PartialViewFolderControllerBase { - public DeletePartialViewFolderController( - IUmbracoMapper mapper, - IPartialViewFolderService partialViewFolderService) - : base(mapper, partialViewFolderService) - { - } + private readonly IPartialViewFolderService _partialViewFolderService; - [HttpDelete] + public DeletePartialViewFolderController(IPartialViewFolderService partialViewFolderService) + => _partialViewFolderService = partialViewFolderService; + + [HttpDelete("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public Task Delete(string path) => DeleteAsync(path); + public async Task Delete(string path) + { + path = DecodePath(path).VirtualPathToSystemPath(); + PartialViewFolderOperationStatus result = await _partialViewFolderService.DeleteAsync(path); + return result is PartialViewFolderOperationStatus.Success + ? Ok() + : OperationStatusResult(result); + } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/PartialViewFolderControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/PartialViewFolderControllerBase.cs index 8491eb1ef2..49c8b36952 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/PartialViewFolderControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/PartialViewFolderControllerBase.cs @@ -4,9 +4,6 @@ using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.Builders; using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Web.Common.Authorization; @@ -16,28 +13,9 @@ namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Folder; [VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.PartialView}/folder")] [ApiExplorerSettings(GroupName = "Partial View")] [Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessPartialViews)] -public class PartialViewFolderControllerBase : PathFolderManagementControllerBase +public class PartialViewFolderControllerBase : FileSystemManagementControllerBase { - private readonly IPartialViewFolderService _partialViewFolderService; - - public PartialViewFolderControllerBase( - IUmbracoMapper mapper, - IPartialViewFolderService partialViewFolderService) - : base(mapper) - { - _partialViewFolderService = partialViewFolderService; - } - - protected override Task GetContainerAsync(string path) => _partialViewFolderService.GetAsync(path); - - protected override Task> CreateContainerAsync( - PathContainer container) => - _partialViewFolderService.CreateAsync(container); - - protected override Task> DeleteContainerAsync(string path) => - _partialViewFolderService.DeleteAsync(path); - - protected override IActionResult OperationStatusResult(PartialViewFolderOperationStatus status) => + protected IActionResult OperationStatusResult(PartialViewFolderOperationStatus status) => status switch { PartialViewFolderOperationStatus.AlreadyExists => BadRequest(new ProblemDetailsBuilder() diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Items/ItemPartialViewItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/ItemPartialViewItemController.cs similarity index 55% rename from src/Umbraco.Cms.Api.Management/Controllers/PartialView/Items/ItemPartialViewItemController.cs rename to src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/ItemPartialViewItemController.cs index 0a7847df50..060c0e9658 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Items/ItemPartialViewItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/ItemPartialViewItemController.cs @@ -1,30 +1,27 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.PartialView.Item; -using Umbraco.Cms.Core.IO; -namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Items; +namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Item; [ApiVersion("1.0")] public class ItemPartialViewItemController : PartialViewItemControllerBase { private readonly IFileItemPresentationModelFactory _fileItemPresentationModelFactory; - private readonly IFileSystem _fileSystem; - public ItemPartialViewItemController(IFileItemPresentationModelFactory fileItemPresentationModelFactory, FileSystems fileSystems) - { - _fileItemPresentationModelFactory = fileItemPresentationModelFactory; - _fileSystem = fileSystems.PartialViewsFileSystem ?? throw new ArgumentException("Missing partial views file system", nameof(fileSystems)); - } + public ItemPartialViewItemController(IFileItemPresentationModelFactory fileItemPresentationModelFactory) + => _fileItemPresentationModelFactory = fileItemPresentationModelFactory; [HttpGet("item")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public async Task Item([FromQuery(Name = "id")] HashSet paths) + public async Task Item([FromQuery(Name = "path")] HashSet paths) { - IEnumerable responseModels = _fileItemPresentationModelFactory.CreatePartialViewResponseModels(paths, _fileSystem); - return Ok(responseModels); + paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet(); + IEnumerable responseModels = _fileItemPresentationModelFactory.CreatePartialViewItemResponseModels(paths); + return await Task.FromResult(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Items/PartialViewItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/PartialViewItemControllerBase.cs similarity index 99% rename from src/Umbraco.Cms.Api.Management/Controllers/PartialView/Items/PartialViewItemControllerBase.cs rename to src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/PartialViewItemControllerBase.cs index 4137c5499a..f844fc72a6 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Items/PartialViewItemControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/PartialViewItemControllerBase.cs @@ -4,7 +4,7 @@ using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Core; using Umbraco.Cms.Web.Common.Authorization; -namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Items; +namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Item; [ApiController] [VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.PartialView}")] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/PartialViewControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/PartialViewControllerBase.cs index 65a7e6748e..113922b1fe 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/PartialViewControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/PartialViewControllerBase.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.PartialView; [VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.PartialView}")] [ApiExplorerSettings(GroupName = "Partial View")] [Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessPartialViews)] -public class PartialViewControllerBase : ManagementApiControllerBase +public class PartialViewControllerBase : FileSystemManagementControllerBase { protected IActionResult PartialViewOperationStatusResult(PartialViewOperationStatus status) => status switch diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/RenamePartialViewController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/RenamePartialViewController.cs new file mode 100644 index 0000000000..68dec8e993 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/RenamePartialViewController.cs @@ -0,0 +1,45 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.PartialView; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.PartialView; + +[ApiVersion("1.0")] +public class RenamePartialViewController : PartialViewControllerBase +{ + private readonly IPartialViewService _partialViewService; + private readonly IUmbracoMapper _umbracoMapper; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public RenamePartialViewController(IPartialViewService partialViewService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IUmbracoMapper umbracoMapper) + { + _partialViewService = partialViewService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _umbracoMapper = umbracoMapper; + } + + [HttpPut("{path}/rename")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Rename(string path, RenamePartialViewRequestModel requestModel) + { + PartialViewRenameModel renameModel = _umbracoMapper.Map(requestModel)!; + + path = DecodePath(path).VirtualPathToSystemPath(); + Attempt renameAttempt = await _partialViewService.RenameAsync(path, renameModel, CurrentUserKey(_backOfficeSecurityAccessor)); + + return renameAttempt.Success + ? CreatedAtPath(controller => nameof(controller.ByPath), renameAttempt.Result!.Path.SystemPathToVirtualPath()) + : PartialViewOperationStatusResult(renameAttempt.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Snippet/ByNameController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Snippet/ByIdController.cs similarity index 62% rename from src/Umbraco.Cms.Api.Management/Controllers/PartialView/Snippet/ByNameController.cs rename to src/Umbraco.Cms.Api.Management/Controllers/PartialView/Snippet/ByIdController.cs index ffebf19866..9cbd830642 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Snippet/ByNameController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Snippet/ByIdController.cs @@ -9,32 +9,26 @@ using Umbraco.Cms.Core.Snippets; namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Snippet; [ApiVersion("1.0")] -public class ByNameController : PartialViewControllerBase +public class ByIdController : PartialViewControllerBase { private readonly IPartialViewService _partialViewService; private readonly IUmbracoMapper _umbracoMapper; - public ByNameController(IPartialViewService partialViewService, IUmbracoMapper umbracoMapper) + public ByIdController(IPartialViewService partialViewService, IUmbracoMapper umbracoMapper) { _partialViewService = partialViewService; _umbracoMapper = umbracoMapper; } - [HttpGet("snippet/{name}")] + [HttpGet("snippet/{id}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PartialViewSnippetResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public async Task GetByName(string name) + public async Task GetById(string id) { - PartialViewSnippet? snippet = await _partialViewService.GetSnippetByNameAsync(name); - - if (snippet is null) - { - return PartialViewNotFound(); - } - - PartialViewSnippetResponseModel? viewModel = _umbracoMapper.Map(snippet); - - return Ok(viewModel); + PartialViewSnippet? snippet = await _partialViewService.GetSnippetAsync(id); + return snippet is not null + ? Ok(_umbracoMapper.Map(snippet)!) + : PartialViewNotFound(); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Snippet/GetAllController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Snippet/GetAllController.cs index 356dae3ef7..c126f17761 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Snippet/GetAllController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Snippet/GetAllController.cs @@ -3,8 +3,10 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels.PartialView.Snippets; +using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Snippets; namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Snippet; @@ -12,20 +14,25 @@ namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Snippet; public class GetAllController : PartialViewControllerBase { private readonly IPartialViewService _partialViewService; + private readonly IUmbracoMapper _umbracoMapper; - public GetAllController(IPartialViewService partialViewService) => _partialViewService = partialViewService; + public GetAllController(IPartialViewService partialViewService, IUmbracoMapper umbracoMapper) + { + _partialViewService = partialViewService; + _umbracoMapper = umbracoMapper; + } [HttpGet("snippet")] [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] public async Task GetAll(int skip = 0, int take = 100) { - PagedModel snippets = await _partialViewService.GetSnippetNamesAsync(skip, take); + PagedModel snippets = await _partialViewService.GetSnippetsAsync(skip, take); - var pageViewModel = new PagedViewModel + var pageViewModel = new PagedViewModel { Total = snippets.Total, - Items = snippets.Items.Select(x => new SnippetItemResponseModel(x)), + Items = _umbracoMapper.MapEnumerable(snippets.Items) }; return Ok(pageViewModel); diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Tree/ChildrenPartialViewTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Tree/ChildrenPartialViewTreeController.cs index cf723744d9..8cde0c3d32 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Tree/ChildrenPartialViewTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Tree/ChildrenPartialViewTreeController.cs @@ -18,6 +18,6 @@ public class ChildrenPartialViewTreeController : PartialViewTreeControllerBase [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task>> Children(string path, int skip = 0, int take = 100) - => await GetChildren(path, skip, take); + public async Task>> Children(string parentPath, int skip = 0, int take = 100) + => await GetChildren(parentPath, skip, take); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/UpdatePartialViewController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/UpdatePartialViewController.cs index 6731e2d466..94e344f549 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/UpdatePartialViewController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/UpdatePartialViewController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.ViewModels.PartialView; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; @@ -28,13 +29,17 @@ public class UpdatePartialViewController : PartialViewControllerBase _mapper = mapper; } - [HttpPut] + [HttpPut("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task Update(UpdatePartialViewRequestModel updateViewModel) + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Update(string path, UpdatePartialViewRequestModel updateViewModel) { + path = DecodePath(path).VirtualPathToSystemPath(); PartialViewUpdateModel updateModel = _mapper.Map(updateViewModel)!; - Attempt updateAttempt = await _partialViewService.UpdateAsync(updateModel, CurrentUserKey(_backOfficeSecurityAccessor)); + + Attempt updateAttempt = await _partialViewService.UpdateAsync(path, updateModel, CurrentUserKey(_backOfficeSecurityAccessor)); return updateAttempt.Success ? Ok() diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PathFolderManagementControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/PathFolderManagementControllerBase.cs deleted file mode 100644 index a6a74bb07f..0000000000 --- a/src/Umbraco.Cms.Api.Management/Controllers/PathFolderManagementControllerBase.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Common.Builders; -using Umbraco.Cms.Api.Management.ViewModels.Folder; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Api.Management.Controllers; - -public abstract class PathFolderManagementControllerBase : ManagementApiControllerBase - where TStatus : Enum -{ - protected readonly IUmbracoMapper Mapper; - - protected PathFolderManagementControllerBase( - IUmbracoMapper mapper) => - Mapper = mapper; - - protected async Task GetFolderAsync(string path) - { - PathContainer? container = await GetContainerAsync(path); - if (container == null) - { - return NotFound(new ProblemDetailsBuilder() - .WithTitle("The container could not be found") - .Build()); - } - - PathFolderResponseModel? viewModel = Mapper.Map(container); - return Ok(viewModel); - } - - protected async Task> CreateAsync(CreatePathFolderRequestModel requestModel) - { - PathContainer folderModel = Mapper.Map(requestModel)!; - - return await CreateContainerAsync(folderModel); - } - - protected async Task DeleteAsync(string path) - { - Attempt attempt = await DeleteContainerAsync(path); - - return attempt.Success - ? Ok() - : OperationStatusResult(attempt.Result!); - } - - protected abstract Task GetContainerAsync(string path); - - protected abstract Task> CreateContainerAsync(PathContainer container); - - protected abstract Task> DeleteContainerAsync(string path); - - protected abstract IActionResult OperationStatusResult(TStatus status); -} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Query/CreateRelationTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Query/CreateRelationTypeController.cs index a12896b3aa..7544af2ffe 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Query/CreateRelationTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Query/CreateRelationTypeController.cs @@ -38,7 +38,7 @@ public class CreateRelationTypeController : RelationTypeControllerBase Attempt result = await _relationService.CreateAsync(relationTypePersisted, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? await Task.FromResult(CreatedAtAction(controller => nameof(controller.ByKey), relationTypePersisted.Key)) + ? CreatedAtId(controller => nameof(controller.ByKey), relationTypePersisted.Key) : RelationTypeOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/ByPathScriptController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/ByPathScriptController.cs index 611a92a3fa..fb9a087157 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/ByPathScriptController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/ByPathScriptController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.ViewModels.Script; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; @@ -22,15 +23,17 @@ public class ByPathScriptController : ScriptControllerBase _mapper = mapper; } - [HttpGet] + [HttpGet("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(ScriptResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task ByPath(string path) { + path = DecodePath(path).VirtualPathToSystemPath(); IScript? script = await _scriptService.GetAsync(path); - return script is null - ? ScriptNotFound() - : Ok(_mapper.Map(script)); + return script is not null + ? Ok(_mapper.Map(script)) + : ScriptNotFound(); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/CreateScriptController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/CreateScriptController.cs index 8d68f2d72a..f9efb7e2bf 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/CreateScriptController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/CreateScriptController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.ViewModels.Script; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; @@ -15,32 +16,31 @@ namespace Umbraco.Cms.Api.Management.Controllers.Script; public class CreateScriptController : ScriptControllerBase { private readonly IScriptService _scriptService; - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly IUmbracoMapper _umbracoMapper; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; public CreateScriptController( IScriptService scriptService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IUmbracoMapper umbracoMapper) + IUmbracoMapper umbracoMapper, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { _scriptService = scriptService; - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _umbracoMapper = umbracoMapper; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } [HttpPost] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Create(CreateScriptRequestModel requestModel) { - Guid currentUserKey = CurrentUserKey(_backOfficeSecurityAccessor); - ScriptCreateModel createModel = _umbracoMapper.Map(requestModel)!; - - Attempt createAttempt = await _scriptService.CreateAsync(createModel, currentUserKey); + Attempt createAttempt = await _scriptService.CreateAsync(createModel, CurrentUserKey(_backOfficeSecurityAccessor)); return createAttempt.Success - ? CreatedAtAction(controller => nameof(controller.ByPath), new { path = createAttempt.Result!.Path }) + ? CreatedAtPath(controller => nameof(controller.ByPath), createAttempt.Result!.Path.SystemPathToVirtualPath()) : ScriptOperationStatusResult(createAttempt.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/DeleteScriptController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/DeleteScriptController.cs index e984341843..42225efa3d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/DeleteScriptController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/DeleteScriptController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; @@ -21,11 +22,14 @@ public class DeleteScriptController : ScriptControllerBase _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - [HttpDelete] + [HttpDelete("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Delete(string path) { + path = DecodePath(path).VirtualPathToSystemPath(); ScriptOperationStatus operationStatus = await _scriptService.DeleteAsync(path, CurrentUserKey(_backOfficeSecurityAccessor)); return operationStatus is ScriptOperationStatus.Success diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ByPathScriptFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ByPathScriptFolderController.cs index 1e7e4dbb86..57c7f34d28 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ByPathScriptFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ByPathScriptFolderController.cs @@ -1,23 +1,37 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.Script.Folder; using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Services.FileSystem; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Script.Folder; [ApiVersion("1.0")] public class ByPathScriptFolderController : ScriptFolderControllerBase { - public ByPathScriptFolderController( - IUmbracoMapper mapper, - IScriptFolderService scriptFolderService) - : base(mapper, scriptFolderService) + private readonly IScriptFolderService _scriptFolderService; + private readonly IUmbracoMapper _mapper; + + public ByPathScriptFolderController(IScriptFolderService scriptFolderService, IUmbracoMapper mapper) { + _scriptFolderService = scriptFolderService; + _mapper = mapper; } - [HttpGet] + [HttpGet("{path}")] [MapToApiVersion("1.0")] - [ProducesResponseType(StatusCodes.Status200OK)] - public Task ByPath(string path) => GetFolderAsync(path); + [ProducesResponseType(typeof(ScriptFolderResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task ByPath(string path) + { + path = DecodePath(path).VirtualPathToSystemPath(); + ScriptFolderModel? folder = await _scriptFolderService.GetAsync(path); + return folder is not null + ? Ok(_mapper.Map(folder)) + : OperationStatusResult(ScriptFolderOperationStatus.NotFound); + } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/CreateScriptFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/CreateScriptFolderController.cs index c1f67363f1..c638cb4b38 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/CreateScriptFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/CreateScriptFolderController.cs @@ -1,11 +1,12 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Management.ViewModels.Folder; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.Script.Folder; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Services.FileSystem; using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Script.Folder; @@ -13,21 +14,28 @@ namespace Umbraco.Cms.Api.Management.Controllers.Script.Folder; [ApiVersion("1.0")] public class CreateScriptFolderController : ScriptFolderControllerBase { - public CreateScriptFolderController(IUmbracoMapper mapper, IScriptFolderService scriptFolderService) - : base(mapper, scriptFolderService) + private readonly IScriptFolderService _scriptFolderService; + private readonly IUmbracoMapper _mapper; + + public CreateScriptFolderController(IScriptFolderService scriptFolderService, IUmbracoMapper mapper) { + _scriptFolderService = scriptFolderService; + _mapper = mapper; } + [HttpPost] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public async Task Create(CreatePathFolderRequestModel model) + public async Task Create(CreateScriptFolderRequestModel requestModel) { - Attempt result = await CreateAsync(model); + ScriptFolderCreateModel createModel = _mapper.Map(requestModel)!; + Attempt result = await _scriptFolderService.CreateAsync(createModel); + return result.Success - ? CreatedAtAction(controller => nameof(controller.ByPath), new { path = result.Result!.Path }) + ? CreatedAtPath(controller => nameof(controller.ByPath), result.Result!.Path.SystemPathToVirtualPath()) : OperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/DeleteScriptFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/DeleteScriptFolderController.cs index 95f4b64ac4..deb195ad3f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/DeleteScriptFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/DeleteScriptFolderController.cs @@ -1,23 +1,31 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Core.Services.FileSystem; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Script.Folder; [ApiVersion("1.0")] public class DeleteScriptFolderController : ScriptFolderControllerBase { - public DeleteScriptFolderController(IUmbracoMapper mapper, IScriptFolderService scriptFolderService) - : base(mapper, scriptFolderService) - { - } + private readonly IScriptFolderService _scriptFolderService; - [HttpDelete] + public DeleteScriptFolderController(IScriptFolderService scriptFolderService) + => _scriptFolderService = scriptFolderService; + + [HttpDelete("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public Task Delete(string path) => DeleteAsync(path); + public async Task Delete(string path) + { + path = DecodePath(path).VirtualPathToSystemPath(); + ScriptFolderOperationStatus result = await _scriptFolderService.DeleteAsync(path); + return result is ScriptFolderOperationStatus.Success + ? Ok() + : OperationStatusResult(result); + } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ScriptFolderControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ScriptFolderControllerBase.cs index 9737fb4909..22d2d41e26 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ScriptFolderControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ScriptFolderControllerBase.cs @@ -4,9 +4,6 @@ using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.Builders; using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Web.Common.Authorization; @@ -16,28 +13,9 @@ namespace Umbraco.Cms.Api.Management.Controllers.Script.Folder; [VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Script}/folder")] [ApiExplorerSettings(GroupName = "Script")] [Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessScripts)] -public class ScriptFolderControllerBase : PathFolderManagementControllerBase +public class ScriptFolderControllerBase : FileSystemManagementControllerBase { - private readonly IScriptFolderService _scriptFolderService; - - public ScriptFolderControllerBase( - IUmbracoMapper mapper, - IScriptFolderService scriptFolderService) - : base(mapper) - { - _scriptFolderService = scriptFolderService; - } - - protected override Task GetContainerAsync(string path) - => _scriptFolderService.GetAsync(path); - - protected override Task> CreateContainerAsync(PathContainer container) - => _scriptFolderService.CreateAsync(container); - - protected override Task> DeleteContainerAsync(string path) => - _scriptFolderService.DeleteAsync(path); - - protected override IActionResult OperationStatusResult(ScriptFolderOperationStatus status) => + protected IActionResult OperationStatusResult(ScriptFolderOperationStatus status) => status switch { ScriptFolderOperationStatus.AlreadyExists => BadRequest(new ProblemDetailsBuilder() diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ItemScriptItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ItemScriptItemController.cs index dcb23788f5..6fad3ac9fa 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ItemScriptItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ItemScriptItemController.cs @@ -1,30 +1,27 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.Script.Item; -using Umbraco.Cms.Core.IO; namespace Umbraco.Cms.Api.Management.Controllers.Script.Item; [ApiVersion("1.0")] public class ItemScriptItemController : ScriptItemControllerBase { - private readonly IFileSystem _fileSystem; private readonly IFileItemPresentationModelFactory _fileItemPresentationModelFactory; - public ItemScriptItemController(FileSystems fileSystems, IFileItemPresentationModelFactory fileItemPresentationModelFactory) - { - _fileSystem = fileSystems.ScriptsFileSystem ?? throw new ArgumentException("Missing scripts file system", nameof(fileSystems)); - _fileItemPresentationModelFactory = fileItemPresentationModelFactory; - } + public ItemScriptItemController(IFileItemPresentationModelFactory fileItemPresentationModelFactory) + => _fileItemPresentationModelFactory = fileItemPresentationModelFactory; [HttpGet("item")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task Item([FromQuery(Name = "path")] HashSet paths) { - IEnumerable reponseModels = _fileItemPresentationModelFactory.CreateScriptItemResponseModels(paths, _fileSystem); - return Ok(reponseModels); + paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet(); + IEnumerable responseModels = _fileItemPresentationModelFactory.CreateScriptItemResponseModels(paths); + return await Task.FromResult(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/RenameScriptController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/RenameScriptController.cs new file mode 100644 index 0000000000..51a61b133d --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/RenameScriptController.cs @@ -0,0 +1,45 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.Script; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.Script; + +[ApiVersion("1.0")] +public class RenameScriptController : ScriptControllerBase +{ + private readonly IScriptService _scriptService; + private readonly IUmbracoMapper _umbracoMapper; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public RenameScriptController(IScriptService scriptService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IUmbracoMapper umbracoMapper) + { + _scriptService = scriptService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _umbracoMapper = umbracoMapper; + } + + [HttpPut("{path}/rename")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Rename(string path, RenameScriptRequestModel requestModel) + { + ScriptRenameModel renameModel = _umbracoMapper.Map(requestModel)!; + + path = DecodePath(path).VirtualPathToSystemPath(); + Attempt renameAttempt = await _scriptService.RenameAsync(path, renameModel, CurrentUserKey(_backOfficeSecurityAccessor)); + + return renameAttempt.Success + ? CreatedAtPath(controller => nameof(controller.ByPath), renameAttempt.Result!.Path.SystemPathToVirtualPath()) + : ScriptOperationStatusResult(renameAttempt.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/ScriptControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/ScriptControllerBase.cs index 9a7985cff7..0ebb645bab 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/ScriptControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/ScriptControllerBase.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Script; [VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Script}")] [ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Script))] [Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessScripts)] -public class ScriptControllerBase : ManagementApiControllerBase +public class ScriptControllerBase : FileSystemManagementControllerBase { protected IActionResult ScriptOperationStatusResult(ScriptOperationStatus status) => status switch diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/Tree/ChildrenScriptTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/Tree/ChildrenScriptTreeController.cs index 320ac3d652..81267a6895 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/Tree/ChildrenScriptTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/Tree/ChildrenScriptTreeController.cs @@ -18,6 +18,6 @@ public class ChildrenScriptTreeController : ScriptTreeControllerBase [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task>> Children(string path, int skip = 0, int take = 100) - => await GetChildren(path, skip, take); + public async Task>> Children(string parentPath, int skip = 0, int take = 100) + => await GetChildren(parentPath, skip, take); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/UpdateScriptController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/UpdateScriptController.cs index 3f2b475420..3ad1a549a3 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/UpdateScriptController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/UpdateScriptController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.ViewModels.Script; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; @@ -15,26 +16,30 @@ namespace Umbraco.Cms.Api.Management.Controllers.Script; public class UpdateScriptController : ScriptControllerBase { private readonly IScriptService _scriptService; - private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; private readonly IUmbracoMapper _umbracoMapper; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; public UpdateScriptController( IScriptService scriptService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - IUmbracoMapper umbracoMapper) + IUmbracoMapper umbracoMapper, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) { _scriptService = scriptService; - _backOfficeSecurityAccessor = backOfficeSecurityAccessor; _umbracoMapper = umbracoMapper; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - [HttpPut] + [HttpPut("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task Update(UpdateScriptRequestModel updateViewModel) + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Update(string path, UpdateScriptRequestModel requestModel) { - ScriptUpdateModel? updateModel = _umbracoMapper.Map(updateViewModel); - Attempt updateAttempt = await _scriptService.UpdateAsync(updateModel!, CurrentUserKey(_backOfficeSecurityAccessor)); + path = DecodePath(path).VirtualPathToSystemPath(); + ScriptUpdateModel updateModel = _umbracoMapper.Map(requestModel)!; + + Attempt updateAttempt = await _scriptService.UpdateAsync(path, updateModel, CurrentUserKey(_backOfficeSecurityAccessor)); return updateAttempt.Success ? Ok() diff --git a/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Item/ItemStaticFileItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Item/ItemStaticFileItemController.cs index cd32230c0c..5a767f7b09 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Item/ItemStaticFileItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Item/ItemStaticFileItemController.cs @@ -1,30 +1,27 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.StaticFile.Item; -using Umbraco.Cms.Core.IO; namespace Umbraco.Cms.Api.Management.Controllers.StaticFile.Item; [ApiVersion("1.0")] public class ItemStaticFileItemController : StaticFileItemControllerBase { - private readonly IFileItemPresentationModelFactory _presentationModelFactory; - private readonly IPhysicalFileSystem _physicalFileSystem; + private readonly IFileItemPresentationModelFactory _fileItemPresentationModelFactory; - public ItemStaticFileItemController(IFileItemPresentationModelFactory presentationModelFactory, IPhysicalFileSystem physicalFileSystem) - { - _presentationModelFactory = presentationModelFactory; - _physicalFileSystem = physicalFileSystem; - } + public ItemStaticFileItemController(IFileItemPresentationModelFactory fileItemPresentationModelFactory) + => _fileItemPresentationModelFactory = fileItemPresentationModelFactory; [HttpGet("item")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task Item([FromQuery(Name = "path")] HashSet paths) { - IEnumerable responseModels = _presentationModelFactory.CreateStaticFileItemResponseModels(paths, _physicalFileSystem); - return Ok(responseModels); + paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet(); + IEnumerable responseModels = _fileItemPresentationModelFactory.CreateStaticFileItemResponseModels(paths); + return await Task.FromResult(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Tree/ChildrenStaticFileTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Tree/ChildrenStaticFileTreeController.cs index 5e932c7951..baa49956e0 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Tree/ChildrenStaticFileTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/StaticFile/Tree/ChildrenStaticFileTreeController.cs @@ -18,6 +18,6 @@ public class ChildrenStaticFileTreeController : StaticFileTreeControllerBase [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task>> Children(string path, int skip = 0, int take = 100) - => await GetChildren(path, skip, take); + public async Task>> Children(string parentPath, int skip = 0, int take = 100) + => await GetChildren(parentPath, skip, take); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/AllStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/AllStylesheetController.cs deleted file mode 100644 index 2f672f2583..0000000000 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/AllStylesheetController.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Common.ViewModels.Pagination; -using Umbraco.Cms.Api.Management.ViewModels.Stylesheet; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; - -namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet; - -[ApiVersion("1.0")] -public class AllStylesheetController : StylesheetControllerBase -{ - private readonly IStylesheetService _stylesheetService; - private readonly IUmbracoMapper _umbracoMapper; - - public AllStylesheetController( - IStylesheetService stylesheetService, - IUmbracoMapper umbracoMapper) - { - _stylesheetService = stylesheetService; - _umbracoMapper = umbracoMapper; - } - - [HttpGet("all")] - [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task All(int skip = 0, int take = 100) - { - IStylesheet[] stylesheets = (await _stylesheetService.GetAllAsync()).ToArray(); - - List viewModels = _umbracoMapper.MapEnumerable(stylesheets.Skip(skip).Take(take)); - - return Ok(new PagedViewModel { Items = viewModels, Total = stylesheets.Length }); - } -} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ByPathStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ByPathStylesheetController.cs index 51a9f5475f..0316cb4518 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ByPathStylesheetController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ByPathStylesheetController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.ViewModels.Stylesheet; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; @@ -22,20 +23,17 @@ public class ByPathStylesheetController : StylesheetControllerBase _umbracoMapper = umbracoMapper; } - [HttpGet] + [HttpGet("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(StylesheetResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task ByPath(string path) { + path = DecodePath(path).VirtualPathToSystemPath(); IStylesheet? stylesheet = await _stylesheetService.GetAsync(path); - if (stylesheet is null) - { - return StylesheetNotFound(); - } - - StylesheetResponseModel? viewModel = _umbracoMapper.Map(stylesheet); - - return Ok(viewModel); + return stylesheet is not null + ? Ok(_umbracoMapper.Map(stylesheet)) + : StylesheetNotFound(); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/CreateStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/CreateStylesheetController.cs index 22c6c20f7a..e0feb72911 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/CreateStylesheetController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/CreateStylesheetController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.ViewModels.Stylesheet; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; @@ -31,14 +32,15 @@ public class CreateStylesheetController : StylesheetControllerBase [HttpPost] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Create(CreateStylesheetRequestModel requestModel) { StylesheetCreateModel createModel = _umbracoMapper.Map(requestModel)!; Attempt createAttempt = await _stylesheetService.CreateAsync(createModel, CurrentUserKey(_backOfficeSecurityAccessor)); return createAttempt.Success - ? CreatedAtAction(controller => nameof(controller.ByPath), new { path = createAttempt.Result!.Path }) + ? CreatedAtPath(controller => nameof(controller.ByPath), createAttempt.Result!.Path.SystemPathToVirtualPath()) : StylesheetOperationStatusResult(createAttempt.Status); } - } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs index f09659962b..4d90641d75 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; @@ -21,11 +22,14 @@ public class DeleteStylesheetController : StylesheetControllerBase _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - [HttpDelete] + [HttpDelete("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Delete(string path) { + path = DecodePath(path).VirtualPathToSystemPath(); StylesheetOperationStatus operationStatus = await _stylesheetService.DeleteAsync(path, CurrentUserKey(_backOfficeSecurityAccessor)); return operationStatus is StylesheetOperationStatus.Success diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ExtractRichTextRulesController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ExtractRichTextRulesController.cs deleted file mode 100644 index 09b264e94a..0000000000 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ExtractRichTextRulesController.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Management.ViewModels.RichTextStylesheet; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Strings.Css; - -namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet; - -[ApiVersion("1.0")] -public class ExtractRichTextRulesController : StylesheetControllerBase -{ - private readonly IRichTextStylesheetService _richTextStylesheetService; - private readonly IUmbracoMapper _umbracoMapper; - - public ExtractRichTextRulesController( - IRichTextStylesheetService richTextStylesheetService, - IUmbracoMapper umbracoMapper) - { - _richTextStylesheetService = richTextStylesheetService; - _umbracoMapper = umbracoMapper; - } - - [HttpPost("rich-text/extract-rules")] - [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(ExtractRichTextStylesheetRulesResponseModel), StatusCodes.Status200OK)] - public async Task ExtractRichTextRules(ExtractRichTextStylesheetRulesRequestModel requestModel) - { - RichTextStylesheetData? model = _umbracoMapper.Map(requestModel); - - IEnumerable rules = await _richTextStylesheetService.ExtractRichTextRules(model!); - - return Ok(new ExtractRichTextStylesheetRulesResponseModel - { - Rules = _umbracoMapper.MapEnumerable(rules), - }); - } -} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/ByPathStylesheetFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/ByPathStylesheetFolderController.cs index 2bbb2c7689..3677f4ea64 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/ByPathStylesheetFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/ByPathStylesheetFolderController.cs @@ -1,20 +1,39 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.Folder; +using Umbraco.Cms.Api.Management.ViewModels.Stylesheet.Folder; using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models.FileSystem; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.FileSystem; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Folder; [ApiVersion("1.0")] public class ByPathStylesheetFolderController : StylesheetFolderControllerBase { - public ByPathStylesheetFolderController(IUmbracoMapper mapper, IStylesheetFolderService stylesheetFolderService) : base(mapper, stylesheetFolderService) + private readonly IStylesheetFolderService _stylesheetFolderService; + private readonly IUmbracoMapper _mapper; + + public ByPathStylesheetFolderController(IStylesheetFolderService stylesheetFolderService, IUmbracoMapper mapper) { + _stylesheetFolderService = stylesheetFolderService; + _mapper = mapper; } - [HttpGet] + [HttpGet("{path}")] [MapToApiVersion("1.0")] - [ProducesResponseType(StatusCodes.Status200OK)] - public Task ByPath(string path) => GetFolderAsync(path); + [ProducesResponseType(typeof(StylesheetFolderResponseModel), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task ByPath(string path) + { + path = DecodePath(path).VirtualPathToSystemPath(); + StylesheetFolderModel? folder = await _stylesheetFolderService.GetAsync(path); + return folder is not null + ? Ok(_mapper.Map(folder)) + : OperationStatusResult(StylesheetFolderOperationStatus.NotFound); + } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/CreateStylesheetFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/CreateStylesheetFolderController.cs index c1c90510c8..e33b330524 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/CreateStylesheetFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/CreateStylesheetFolderController.cs @@ -1,11 +1,12 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Management.ViewModels.Folder; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.Stylesheet.Folder; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Services.FileSystem; using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Folder; @@ -13,9 +14,13 @@ namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Folder; [ApiVersion("1.0")] public class CreateStylesheetFolderController : StylesheetFolderControllerBase { - public CreateStylesheetFolderController(IUmbracoMapper mapper, IStylesheetFolderService stylesheetFolderService) - : base(mapper, stylesheetFolderService) + private readonly IStylesheetFolderService _stylesheetFolderService; + private readonly IUmbracoMapper _mapper; + + public CreateStylesheetFolderController(IStylesheetFolderService stylesheetFolderService, IUmbracoMapper mapper) { + _stylesheetFolderService = stylesheetFolderService; + _mapper = mapper; } @@ -24,11 +29,13 @@ public class CreateStylesheetFolderController : StylesheetFolderControllerBase [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public async Task Create(CreatePathFolderRequestModel model) + public async Task Create(CreateStylesheetFolderRequestModel requestModel) { - Attempt result = await CreateAsync(model); + StylesheetFolderCreateModel createModel = _mapper.Map(requestModel)!; + Attempt result = await _stylesheetFolderService.CreateAsync(createModel); + return result.Success - ? CreatedAtAction(controller => nameof(controller.ByPath), new { path = result.Result!.Path }) + ? CreatedAtPath(controller => nameof(controller.ByPath), result.Result!.Path.SystemPathToVirtualPath()) : OperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/DeleteStylesheetFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/DeleteStylesheetFolderController.cs index a27b16b8f6..945e46fb17 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/DeleteStylesheetFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/DeleteStylesheetFolderController.cs @@ -1,23 +1,31 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Core.Services.FileSystem; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Folder; [ApiVersion("1.0")] public class DeleteStylesheetFolderController : StylesheetFolderControllerBase { - public DeleteStylesheetFolderController(IUmbracoMapper mapper, IStylesheetFolderService stylesheetFolderService) - : base(mapper, stylesheetFolderService) - { - } + private readonly IStylesheetFolderService _stylesheetFolderService; - [HttpDelete] + public DeleteStylesheetFolderController(IStylesheetFolderService stylesheetFolderService) + => _stylesheetFolderService = stylesheetFolderService; + + [HttpDelete("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public Task Delete(string path) => DeleteAsync(path); + public async Task Delete(string path) + { + path = DecodePath(path).VirtualPathToSystemPath(); + StylesheetFolderOperationStatus result = await _stylesheetFolderService.DeleteAsync(path); + return result is StylesheetFolderOperationStatus.Success + ? Ok() + : OperationStatusResult(result); + } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/StylesheetFolderControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/StylesheetFolderControllerBase.cs index 847d7154d5..158c80aa5a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/StylesheetFolderControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/StylesheetFolderControllerBase.cs @@ -4,9 +4,6 @@ using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.Builders; using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; using Umbraco.Cms.Web.Common.Authorization; @@ -16,27 +13,9 @@ namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Folder; [VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Stylesheet}/folder")] [ApiExplorerSettings(GroupName = "Stylesheet")] [Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessStylesheets)] -public class StylesheetFolderControllerBase : PathFolderManagementControllerBase +public class StylesheetFolderControllerBase : FileSystemManagementControllerBase { - private readonly IStylesheetFolderService _stylesheetFolderService; - - public StylesheetFolderControllerBase( - IUmbracoMapper mapper, - IStylesheetFolderService stylesheetFolderService) - : base(mapper) - { - _stylesheetFolderService = stylesheetFolderService; - } - - protected override Task GetContainerAsync(string path) => _stylesheetFolderService.GetAsync(path); - - protected override Task> CreateContainerAsync(PathContainer container) - => _stylesheetFolderService.CreateAsync(container); - - protected override Task> DeleteContainerAsync(string path) - => _stylesheetFolderService.DeleteAsync(path); - - protected override IActionResult OperationStatusResult(StylesheetFolderOperationStatus status) => + protected IActionResult OperationStatusResult(StylesheetFolderOperationStatus status) => status switch { StylesheetFolderOperationStatus.AlreadyExists => BadRequest(new ProblemDetailsBuilder() diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/GetRichTextRulesByPathController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/GetRichTextRulesByPathController.cs deleted file mode 100644 index b652dd769b..0000000000 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/GetRichTextRulesByPathController.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Management.ViewModels.RichTextStylesheet; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.OperationStatus; -using StylesheetRule = Umbraco.Cms.Core.Strings.Css.StylesheetRule; - -namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet; - -[ApiVersion("1.0")] -public class GetRichTextRulesByPathController : StylesheetControllerBase -{ - private readonly IRichTextStylesheetService _richTextStylesheetService; - private readonly IUmbracoMapper _umbracoMapper; - - public GetRichTextRulesByPathController( - IRichTextStylesheetService richTextStylesheetService, - IUmbracoMapper umbracoMapper) - { - _richTextStylesheetService = richTextStylesheetService; - _umbracoMapper = umbracoMapper; - } - - [HttpGet("rich-text/rules")] - [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(RichTextStylesheetRulesResponseModel), StatusCodes.Status200OK)] - public async Task GetByPath(string path) - { - Attempt, StylesheetOperationStatus> rulesAttempt = await _richTextStylesheetService.GetRulesByPathAsync(path); - - if (rulesAttempt.Success is false) - { - return StylesheetOperationStatusResult(rulesAttempt.Status); - } - - return Ok(new RichTextStylesheetRulesResponseModel - { - Rules = _umbracoMapper.MapEnumerable(rulesAttempt.Result) - }); - } -} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/InterpolateRichTextRulesController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/InterpolateRichTextRulesController.cs deleted file mode 100644 index 711ede81d1..0000000000 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/InterpolateRichTextRulesController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Api.Management.ViewModels.RichTextStylesheet; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services; - -namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet; - -[ApiVersion("1.0")] -public class InterpolateRichTextRulesController : StylesheetControllerBase -{ - private readonly IRichTextStylesheetService _richTextStylesheetService; - private readonly IUmbracoMapper _umbracoMapper; - - public InterpolateRichTextRulesController( - IRichTextStylesheetService richTextStylesheetService, - IUmbracoMapper umbracoMapper) - { - _richTextStylesheetService = richTextStylesheetService; - _umbracoMapper = umbracoMapper; - } - - [HttpPost("rich-text/interpolate-rules")] - [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(InterpolateRichTextStylesheetResponseModel), StatusCodes.Status200OK)] - public async Task InterpolateRichTextRules(InterpolateRichTextStylesheetRequestModel requestModel) - { - RichTextStylesheetData? model = _umbracoMapper.Map(requestModel); - - var content = await _richTextStylesheetService.InterpolateRichTextRules(model!); - - return Ok(new InterpolateRichTextStylesheetResponseModel - { - Content = content, - }); - } -} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/ItemStylesheetItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/ItemStylesheetItemController.cs index 59aadd80bc..6e66d159dc 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/ItemStylesheetItemController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/ItemStylesheetItemController.cs @@ -1,10 +1,9 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.Factories; -using Umbraco.Cms.Api.Management.ViewModels.Script.Item; using Umbraco.Cms.Api.Management.ViewModels.Stylesheet.Item; -using Umbraco.Cms.Core.IO; namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Item; @@ -12,20 +11,17 @@ namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Item; public class ItemStylesheetItemController : StylesheetItemControllerBase { private readonly IFileItemPresentationModelFactory _fileItemPresentationModelFactory; - private readonly IFileSystem _fileSystem; - public ItemStylesheetItemController(FileSystems fileSystems, IFileItemPresentationModelFactory fileItemPresentationModelFactory) - { - _fileItemPresentationModelFactory = fileItemPresentationModelFactory; - _fileSystem = fileSystems.StylesheetsFileSystem ?? throw new ArgumentException("Missing stylesheet file system", nameof(fileSystems)); - } + public ItemStylesheetItemController(IFileItemPresentationModelFactory fileItemPresentationModelFactory) + => _fileItemPresentationModelFactory = fileItemPresentationModelFactory; [HttpGet("item")] [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task Item([FromQuery(Name = "path")] HashSet paths) { - IEnumerable responseModels = _fileItemPresentationModelFactory.CreateStylesheetItemResponseModels(paths, _fileSystem); - return Ok(responseModels); + paths = paths.Select(path => path.VirtualPathToSystemPath()).ToHashSet(); + IEnumerable responseModels = _fileItemPresentationModelFactory.CreateStylesheetItemResponseModels(paths); + return await Task.FromResult(Ok(responseModels)); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/RenameStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/RenameStylesheetController.cs new file mode 100644 index 0000000000..127e9084fd --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/RenameStylesheetController.cs @@ -0,0 +1,45 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.Stylesheet; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet; + +[ApiVersion("1.0")] +public class RenameStylesheetController : StylesheetControllerBase +{ + private readonly IStylesheetService _stylesheetService; + private readonly IUmbracoMapper _umbracoMapper; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public RenameStylesheetController(IStylesheetService stylesheetService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor, IUmbracoMapper umbracoMapper) + { + _stylesheetService = stylesheetService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _umbracoMapper = umbracoMapper; + } + + [HttpPut("{path}/rename")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Rename(string path, RenameStylesheetRequestModel requestModel) + { + StylesheetRenameModel renameModel = _umbracoMapper.Map(requestModel)!; + + path = DecodePath(path).VirtualPathToSystemPath(); + Attempt renameAttempt = await _stylesheetService.RenameAsync(path, renameModel, CurrentUserKey(_backOfficeSecurityAccessor)); + + return renameAttempt.Success + ? CreatedAtPath(controller => nameof(controller.ByPath), renameAttempt.Result!.Path.SystemPathToVirtualPath()) + : StylesheetOperationStatusResult(renameAttempt.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/StylesheetControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/StylesheetControllerBase.cs index 28066a8fda..465554fd71 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/StylesheetControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/StylesheetControllerBase.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet; [VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Stylesheet}")] [ApiExplorerSettings(GroupName = "Stylesheet")] [Authorize(Policy = "New" + AuthorizationPolicies.TreeAccessStylesheets)] -public class StylesheetControllerBase : ManagementApiControllerBase +public class StylesheetControllerBase : FileSystemManagementControllerBase { protected IActionResult StylesheetOperationStatusResult(StylesheetOperationStatus status) => status switch diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Tree/ChildrenStylesheetTreeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Tree/ChildrenStylesheetTreeController.cs index b986e83c2c..cb452e64fe 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Tree/ChildrenStylesheetTreeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Tree/ChildrenStylesheetTreeController.cs @@ -18,6 +18,6 @@ public class ChildrenStylesheetTreeController : StylesheetTreeControllerBase [HttpGet("children")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task>> Children(string path, int skip = 0, int take = 100) - => await GetChildren(path, skip, take); + public async Task>> Children(string parentPath, int skip = 0, int take = 100) + => await GetChildren(parentPath, skip, take); } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs index 114b03a1a7..7a9c743e77 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs @@ -1,6 +1,7 @@ using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.ViewModels.Stylesheet; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; @@ -28,14 +29,17 @@ public class UpdateStylesheetController : StylesheetControllerBase _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - [HttpPut] + [HttpPut("{path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task Update(UpdateStylesheetRequestModel requestModel) + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task Update(string path, UpdateStylesheetRequestModel requestModel) { + path = DecodePath(path).VirtualPathToSystemPath(); StylesheetUpdateModel updateModel = _umbracoMapper.Map(requestModel)!; - Attempt updateAttempt = await _stylesheetService.UpdateAsync(updateModel, CurrentUserKey(_backOfficeSecurityAccessor)); + Attempt updateAttempt = await _stylesheetService.UpdateAsync(path, updateModel, CurrentUserKey(_backOfficeSecurityAccessor)); return updateAttempt.Success ? Ok() diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/CreateTemplateController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/CreateTemplateController.cs index f41188fee7..b04fa462f9 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Template/CreateTemplateController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/CreateTemplateController.cs @@ -39,7 +39,7 @@ public class CreateTemplateController : TemplateControllerBase requestModel.Key); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result.Key) : TemplateOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/TemporaryFile/CreateTemporaryFileController.cs b/src/Umbraco.Cms.Api.Management/Controllers/TemporaryFile/CreateTemporaryFileController.cs index 68f33acd69..59690d46ac 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/TemporaryFile/CreateTemporaryFileController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/TemporaryFile/CreateTemporaryFileController.cs @@ -33,7 +33,7 @@ public class CreateTemporaryFileController : TemporaryFileControllerBase Attempt result = await _temporaryFileService.CreateAsync(createModel); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), new { id = result.Result!.Key }) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result!.Key) : TemporaryFileStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Tree/FileSystemTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Tree/FileSystemTreeControllerBase.cs index f734825b3c..604e5a82be 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Tree/FileSystemTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Tree/FileSystemTreeControllerBase.cs @@ -2,6 +2,8 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Api.Management.Services.Paging; using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; using Umbraco.Cms.Api.Management.ViewModels.Tree; using Umbraco.Extensions; @@ -39,21 +41,6 @@ public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase return await Task.FromResult(Ok(result)); } - protected async Task>> GetItems(string[] paths) - { - FileSystemTreeItemPresentationModel[] viewModels = paths - .Where(FileSystem.FileExists) - .Select(path => - { - var fileName = GetFileName(path); - return fileName.IsNullOrWhiteSpace() - ? null - : MapViewModel(path, fileName, false); - }).WhereNotNull().ToArray(); - - return await Task.FromResult(Ok(viewModels)); - } - protected virtual string[] GetDirectories(string path) => FileSystem .GetDirectories(path) .OrderBy(directory => directory) @@ -64,13 +51,12 @@ public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase .OrderBy(file => file) .ToArray(); - protected virtual string GetFileName(string path) => FileSystem.GetFileName(path); - protected virtual bool DirectoryHasChildren(string path) => FileSystem.GetFiles(path).Any() || FileSystem.GetDirectories(path).Any(); private FileSystemTreeItemPresentationModel[] GetPathViewModels(string path, long pageNumber, int pageSize, out long totalItems) { + path = path.VirtualPathToSystemPath(); var allItems = GetDirectories(path) .Select(directory => new { Path = directory, IsFolder = true }) .Union(GetFiles(path).Select(file => new { Path = file, IsFolder = false })) @@ -95,12 +81,18 @@ public abstract class FileSystemTreeControllerBase : ManagementApiControllerBase => new() { Total = totalItems, Items = viewModels }; private FileSystemTreeItemPresentationModel MapViewModel(string path, string name, bool isFolder) - => new() + { + var parentPath = Path.GetDirectoryName(path); + return new FileSystemTreeItemPresentationModel { - Path = path, + Path = path.SystemPathToVirtualPath(), Name = name, HasChildren = isFolder && DirectoryHasChildren(path), Type = ItemType(path), - IsFolder = isFolder + IsFolder = isFolder, + Parent = parentPath.IsNullOrWhiteSpace() + ? null + : new FileSystemFolderModel { Path = parentPath.SystemPathToVirtualPath() } }; + } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/User/InviteUsersController.cs b/src/Umbraco.Cms.Api.Management/Controllers/User/InviteUsersController.cs index 5fe0408932..082be71063 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/User/InviteUsersController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/User/InviteUsersController.cs @@ -40,7 +40,7 @@ public class InviteUserController : UserControllerBase Attempt result = await _userService.InviteAsync(CurrentUserKey(_backOfficeSecurityAccessor), userInvite); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), result.Result.InvitedUser!.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), result.Result.InvitedUser!.Key) : UserOperationStatusResult(result.Status, result.Result); } } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/UserGroup/CreateUserGroupController.cs b/src/Umbraco.Cms.Api.Management/Controllers/UserGroup/CreateUserGroupController.cs index 1340c6c9c6..0ba99bc38f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/UserGroup/CreateUserGroupController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/UserGroup/CreateUserGroupController.cs @@ -51,7 +51,7 @@ public class CreateUserGroupController : UserGroupControllerBase Attempt result = await _userGroupService.CreateAsync(group, CurrentUserKey(_backOfficeSecurityAccessor)); return result.Success - ? CreatedAtAction(controller => nameof(controller.ByKey), group.Key) + ? CreatedAtId(controller => nameof(controller.ByKey), group.Key) : UserGroupOperationStatusResult(result.Status); } } diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeCorsPolicyBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeCorsPolicyBuilderExtensions.cs index 5baee9de6b..da81e5de7f 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeCorsPolicyBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeCorsPolicyBuilderExtensions.cs @@ -34,6 +34,7 @@ internal static class BackOfficeCorsPolicyBuilderExtensions { policy .WithOrigins(customOrigin) + .WithExposedHeaders(Constants.Headers.Location, Constants.Headers.GeneratedResource) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/PathFolderBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/PathFolderBuilderExtensions.cs deleted file mode 100644 index a50e3a4609..0000000000 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/PathFolderBuilderExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Umbraco.Cms.Api.Management.Mapping.Folder; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Mapping; - -namespace Umbraco.Cms.Api.Management.DependencyInjection; - -public static class PathFolderBuilderExtensions -{ - internal static IUmbracoBuilder AddPathFolders(this IUmbracoBuilder builder) - { - builder.WithCollectionBuilder() - .Add(); - - return builder; - } -} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs index 199e822ed5..0e3bb6d0fa 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UmbracoBuilderExtensions.cs @@ -47,7 +47,6 @@ public static class UmbracoBuilderExtensions .AddTours() .AddPackages() .AddEntities() - .AddPathFolders() .AddScripts() .AddPartialViews() .AddStylesheets() diff --git a/src/Umbraco.Cms.Api.Management/Extensions/StringPathExtensions.cs b/src/Umbraco.Cms.Api.Management/Extensions/StringPathExtensions.cs new file mode 100644 index 0000000000..a17f0b4025 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Extensions/StringPathExtensions.cs @@ -0,0 +1,10 @@ +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Extensions; + +internal static class StringPathExtensions +{ + public static string SystemPathToVirtualPath(this string systemPath) => systemPath.Replace('\\', '/').TrimStart('~').EnsureStartsWith('/'); + + public static string VirtualPathToSystemPath(this string virtualPath) => virtualPath.TrimStart('/').Replace('/', Path.DirectorySeparatorChar); +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/FileItemPresentationModelFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/FileItemPresentationModelFactory.cs index 440f4770e1..c1a38a0c1d 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/FileItemPresentationModelFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/FileItemPresentationModelFactory.cs @@ -1,8 +1,9 @@ -using Umbraco.Cms.Api.Management.ViewModels.PartialView.Item; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.PartialView.Item; using Umbraco.Cms.Api.Management.ViewModels.Script.Item; using Umbraco.Cms.Api.Management.ViewModels.StaticFile.Item; using Umbraco.Cms.Api.Management.ViewModels.Stylesheet.Item; -using Umbraco.Cms.Core; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; using Umbraco.Cms.Core.IO; using Umbraco.Extensions; @@ -10,31 +11,52 @@ namespace Umbraco.Cms.Api.Management.Factories; public class FileItemPresentationModelFactory : IFileItemPresentationModelFactory { - public IEnumerable CreatePartialViewResponseModels(IEnumerable paths, IFileSystem fileSystem) => - paths.Select( - path => new PartialViewItemResponseModel - { - Path = path, Name = fileSystem.GetFileName(path), Icon = Constants.Icons.PartialView, - }); + private readonly FileSystems _fileSystems; + private readonly IPhysicalFileSystem _physicalFileSystem; - public IEnumerable CreateScriptItemResponseModels(IEnumerable paths, IFileSystem fileSystem) => - paths.Select( - path => new ScriptItemResponseModel - { - Path = path, Name = fileSystem.GetFileName(path), Icon = Constants.Icons.PartialView, - }); + public FileItemPresentationModelFactory(FileSystems fileSystems, IPhysicalFileSystem physicalFileSystem) + { + _fileSystems = fileSystems; + _physicalFileSystem = physicalFileSystem; + } - public IEnumerable CreateStaticFileItemResponseModels(IEnumerable paths, IFileSystem fileSystem) => - paths.Select( - path => new StaticFileItemResponseModel - { - Path = path, Name = fileSystem.GetFileName(path), Icon = Constants.Icons.PartialView, - }); + public IEnumerable CreatePartialViewItemResponseModels(IEnumerable paths) + => CreateItemResponseModels( + paths, + _fileSystems.PartialViewsFileSystem, + (name, path, parent, isFolder) => new() { Name = name, Path = path, Parent = parent, IsFolder = isFolder }); - public IEnumerable CreateStylesheetItemResponseModels(IEnumerable paths, IFileSystem fileSystem) => - paths.Select( - path => new StylesheetItemResponseModel - { - Path = path, Name = fileSystem.GetFileName(path), Icon = Constants.Icons.PartialView, - }); + public IEnumerable CreateScriptItemResponseModels(IEnumerable paths) + => CreateItemResponseModels( + paths, + _fileSystems.ScriptsFileSystem, + (name, path, parent, isFolder) => new() { Name = name, Path = path, Parent = parent, IsFolder = isFolder }); + + public IEnumerable CreateStaticFileItemResponseModels(IEnumerable paths) + => CreateItemResponseModels( + paths, + _physicalFileSystem, + (name, path, parent, isFolder) => new() { Name = name, Path = path, Parent = parent, IsFolder = isFolder }); + + public IEnumerable CreateStylesheetItemResponseModels(IEnumerable paths) + => CreateItemResponseModels( + paths, + _fileSystems.StylesheetsFileSystem, + (name, path, parent, isFolder) => new() { Name = name, Path = path, Parent = parent, IsFolder = isFolder }); + + private IEnumerable CreateItemResponseModels(IEnumerable paths, IFileSystem? fileSystem, Func itemFactory) + where T : FileSystemResponseModelBase + => fileSystem != null + ? paths.Where(path => fileSystem.DirectoryExists(path) || fileSystem.FileExists(path)).Select(path => + { + var fileName = fileSystem.GetFileName(path); + var parentPath = Path.GetDirectoryName(path); + var isFolder = fileSystem.GetExtension(path).IsNullOrWhiteSpace(); + FileSystemFolderModel? parent = parentPath.IsNullOrWhiteSpace() + ? null + : new FileSystemFolderModel { Path = parentPath.SystemPathToVirtualPath() }; + return itemFactory(fileName, path.SystemPathToVirtualPath(), parent, isFolder); + }) + .ToArray() + : throw new ArgumentException("Missing file system", nameof(fileSystem)); } diff --git a/src/Umbraco.Cms.Api.Management/Factories/IFileItemPresentationModelFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IFileItemPresentationModelFactory.cs index f27e23f221..6f766448d6 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/IFileItemPresentationModelFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/IFileItemPresentationModelFactory.cs @@ -8,9 +8,11 @@ namespace Umbraco.Cms.Api.Management.Factories; public interface IFileItemPresentationModelFactory { - IEnumerable CreatePartialViewResponseModels(IEnumerable path, IFileSystem fileSystem); + IEnumerable CreatePartialViewItemResponseModels(IEnumerable path); - IEnumerable CreateScriptItemResponseModels(IEnumerable paths, IFileSystem fileSystem); - IEnumerable CreateStaticFileItemResponseModels(IEnumerable paths, IFileSystem fileSystem); - IEnumerable CreateStylesheetItemResponseModels(IEnumerable paths, IFileSystem fileSystem); + IEnumerable CreateScriptItemResponseModels(IEnumerable paths); + + IEnumerable CreateStaticFileItemResponseModels(IEnumerable paths); + + IEnumerable CreateStylesheetItemResponseModels(IEnumerable paths); } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Folder/PathFolderViewModelMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Folder/PathFolderViewModelMapDefinition.cs deleted file mode 100644 index 0aae43ebc2..0000000000 --- a/src/Umbraco.Cms.Api.Management/Mapping/Folder/PathFolderViewModelMapDefinition.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Umbraco.Cms.Api.Management.ViewModels.Folder; -using Umbraco.Cms.Core.Mapping; -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Api.Management.Mapping.Folder; - -public class PathFolderViewModelMapDefinition : IMapDefinition -{ - public void DefineMaps(IUmbracoMapper mapper) - { - mapper.Define((_, _) => new PathFolderResponseModel(), Map); - mapper.Define((_, _) => new PathContainer { Name = string.Empty }, Map); - } - - // Umbraco.Code.MapAll - private void Map(CreatePathFolderRequestModel source, PathContainer target, MapperContext context) - { - target.Name = source.Name; - target.ParentPath = source.ParentPath; - } - - // Umbraco.Code.MapAll - private void Map(PathContainer source, PathFolderResponseModel target, MapperContext context) - { - target.Name = source.Name; - target.ParentPath = source.ParentPath; - } -} diff --git a/src/Umbraco.Cms.Api.Management/Mapping/PartialView/PartialViewViewModelsMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/PartialView/PartialViewViewModelsMapDefinition.cs index 3f41e02b1b..97e241feff 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/PartialView/PartialViewViewModelsMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/PartialView/PartialViewViewModelsMapDefinition.cs @@ -1,8 +1,14 @@ -using Umbraco.Cms.Api.Management.ViewModels.PartialView; +using Microsoft.IdentityModel.Tokens; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.PartialView; +using Umbraco.Cms.Api.Management.ViewModels.PartialView.Folder; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; using Umbraco.Cms.Api.Management.ViewModels.PartialView.Snippets; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.FileSystem; using Umbraco.Cms.Core.Snippets; +using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Mapping.PartialView; @@ -10,18 +16,16 @@ public class PartialViewViewModelsMapDefinition : IMapDefinition { public void DefineMaps(IUmbracoMapper mapper) { - mapper.Define((_, _) => new PartialViewSnippetResponseModel { Name = string.Empty, Content = string.Empty }, Map); mapper.Define((_, _) => new PartialViewResponseModel { Name = string.Empty, Path = string.Empty, Content = string.Empty }, Map); mapper.Define((_, _) => new PartialViewCreateModel { Name = string.Empty }, Map); - mapper.Define((_, _) => new PartialViewUpdateModel { Content = string.Empty, ExistingPath = string.Empty, Name = string.Empty }, Map); - } + mapper.Define((_, _) => new PartialViewUpdateModel { Content = string.Empty }, Map); + mapper.Define((_, _) => new PartialViewRenameModel { Name = string.Empty }, Map); - // Umbraco.Code.MapAll - private void Map(UpdatePartialViewRequestModel source, PartialViewUpdateModel target, MapperContext context) - { - target.Name = source.Name; - target.Content = source.Content; - target.ExistingPath = source.ExistingPath; + mapper.Define((_, _) => new PartialViewSnippetResponseModel { Id = string.Empty, Name = string.Empty, Content = string.Empty }, Map); + mapper.Define((_, _) => new PartialViewSnippetItemResponseModel { Id = string.Empty, Name = string.Empty }, Map); + + mapper.Define((_, _) => new PartialViewFolderResponseModel { Name = string.Empty, Path = string.Empty }, Map); + mapper.Define((_, _) => new PartialViewFolderCreateModel { Name = string.Empty }, Map); } // Umbraco.Code.MapAll @@ -29,7 +33,14 @@ public class PartialViewViewModelsMapDefinition : IMapDefinition { target.Name = source.Name ?? string.Empty; target.Content = source.Content ?? string.Empty; - target.Path = source.Path; + target.Path = source.Path.SystemPathToVirtualPath(); + var parentPath = Path.GetDirectoryName(source.Path); + target.Parent = parentPath.IsNullOrWhiteSpace() + ? null + : new FileSystemFolderModel + { + Path = parentPath.SystemPathToVirtualPath() + }; } // Umbraco.Code.MapAll @@ -37,13 +48,49 @@ public class PartialViewViewModelsMapDefinition : IMapDefinition { target.Name = source.Name; target.Content = source.Content; - target.ParentPath = source.ParentPath; + target.ParentPath = source.Parent?.Path.VirtualPathToSystemPath(); + } + + // Umbraco.Code.MapAll + private void Map(UpdatePartialViewRequestModel source, PartialViewUpdateModel target, MapperContext context) + => target.Content = source.Content; + + // Umbraco.Code.MapAll + private void Map(RenamePartialViewRequestModel source, PartialViewRenameModel target, MapperContext context) + => target.Name = source.Name; + + // Umbraco.Code.MapAll + private void Map(PartialViewFolderModel source, PartialViewFolderResponseModel target, MapperContext context) + { + target.Path = source.Path.SystemPathToVirtualPath(); + target.Name = source.Name; + target.Parent = source.ParentPath.IsNullOrEmpty() + ? null + : new FileSystemFolderModel + { + Path = source.ParentPath!.SystemPathToVirtualPath() + }; } // Umbraco.Code.MapAll private void Map(PartialViewSnippet source, PartialViewSnippetResponseModel target, MapperContext context) { + target.Id = source.Id; target.Name = source.Name; target.Content = source.Content; } + + // Umbraco.Code.MapAll + private void Map(PartialViewSnippetSlim source, PartialViewSnippetItemResponseModel target, MapperContext context) + { + target.Id = source.Id; + target.Name = source.Name; + } + + // Umbraco.Code.MapAll + private void Map(CreatePartialViewFolderRequestModel source, PartialViewFolderCreateModel target, MapperContext context) + { + target.Name = source.Name; + target.ParentPath = source.Parent?.Path.VirtualPathToSystemPath(); + } } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Script/ScriptViewModelsMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Script/ScriptViewModelsMapDefinition.cs index d6d6fb6fc8..75096091c3 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/Script/ScriptViewModelsMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/Script/ScriptViewModelsMapDefinition.cs @@ -1,6 +1,12 @@ -using Umbraco.Cms.Api.Management.ViewModels.Script; +using Microsoft.IdentityModel.Tokens; +using Umbraco.Cms.Api.Management.Extensions; +using Umbraco.Cms.Api.Management.ViewModels.Script; +using Umbraco.Cms.Api.Management.ViewModels.Script.Folder; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Mapping.Script; @@ -8,17 +14,13 @@ public class ScriptViewModelsMapDefinition : IMapDefinition { public void DefineMaps(IUmbracoMapper mapper) { + mapper.Define((_, _) => new ScriptResponseModel { Name = string.Empty, Path = string.Empty, Content = string.Empty }, Map); mapper.Define((_, _) => new ScriptCreateModel { Name = string.Empty }, Map); - mapper.Define((_, _) => new ScriptUpdateModel { Name = string.Empty, Content = string.Empty, ExistingPath = string.Empty }, Map); - mapper.Define((_, _) => new ScriptResponseModel { Name = string.Empty, Content = string.Empty }, Map); - } + mapper.Define((_, _) => new ScriptUpdateModel { Content = string.Empty }, Map); + mapper.Define((_, _) => new ScriptRenameModel { Name = string.Empty }, Map); - // Umbraco.Code.MapAll - private void Map(UpdateScriptRequestModel source, ScriptUpdateModel target, MapperContext context) - { - target.Name = source.Name; - target.Content = source.Content; - target.ExistingPath = source.ExistingPath; + mapper.Define((_, _) => new ScriptFolderResponseModel { Name = string.Empty, Path = string.Empty }, Map); + mapper.Define((_, _) => new ScriptFolderCreateModel { Name = string.Empty }, Map); } // Umbraco.Code.MapAll @@ -26,14 +28,49 @@ public class ScriptViewModelsMapDefinition : IMapDefinition { target.Name = source.Name ?? string.Empty; target.Content = source.Content ?? string.Empty; - target.Path = source.Path; + target.Path = source.Path.SystemPathToVirtualPath(); + var parentPath = Path.GetDirectoryName(source.Path); + target.Parent = parentPath.IsNullOrWhiteSpace() + ? null + : new FileSystemFolderModel + { + Path = parentPath.SystemPathToVirtualPath() + }; } // Umbraco.Code.MapAll private void Map(CreateScriptRequestModel source, ScriptCreateModel target, MapperContext context) { target.Name = source.Name; - target.ParentPath = source.ParentPath; target.Content = source.Content; + target.ParentPath = source.Parent?.Path.VirtualPathToSystemPath(); + } + + // Umbraco.Code.MapAll + private void Map(UpdateScriptRequestModel source, ScriptUpdateModel target, MapperContext context) + => target.Content = source.Content; + + // Umbraco.Code.MapAll + private void Map(RenameScriptRequestModel source, ScriptRenameModel target, MapperContext context) + => target.Name = source.Name; + + // Umbraco.Code.MapAll + private void Map(ScriptFolderModel source, ScriptFolderResponseModel target, MapperContext context) + { + target.Path = source.Path.SystemPathToVirtualPath(); + target.Name = source.Name; + target.Parent = source.ParentPath.IsNullOrEmpty() + ? null + : new FileSystemFolderModel + { + Path = source.ParentPath!.SystemPathToVirtualPath() + }; + } + + // Umbraco.Code.MapAll + private void Map(CreateScriptFolderRequestModel source, ScriptFolderCreateModel target, MapperContext context) + { + target.Name = source.Name; + target.ParentPath = source.Parent?.Path.VirtualPathToSystemPath(); } } diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Stylesheet/StylesheetviewModelsMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Stylesheet/StylesheetviewModelsMapDefinition.cs index 66272078ff..30aa1651ec 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/Stylesheet/StylesheetviewModelsMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/Stylesheet/StylesheetviewModelsMapDefinition.cs @@ -1,8 +1,12 @@ -using Umbraco.Cms.Api.Management.ViewModels.RichTextStylesheet; +using Microsoft.IdentityModel.Tokens; +using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Api.Management.ViewModels.Stylesheet; +using Umbraco.Cms.Api.Management.ViewModels.Stylesheet.Folder; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Strings.Css; +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.Mapping.Stylesheet; @@ -12,56 +16,11 @@ public class StylesheetViewModelsMapDefinition : IMapDefinition { mapper.Define((_, _) => new StylesheetResponseModel { Content = string.Empty, Name = string.Empty, Path = string.Empty }, Map); mapper.Define((_, _) => new StylesheetCreateModel { Name = string.Empty }, Map); - mapper.Define((_, _) => new StylesheetUpdateModel { Content = string.Empty, Name = string.Empty, ExistingPath = string.Empty }, Map); - mapper.Define((_, _) => new RichTextStylesheetData(), Map); - mapper.Define((_, _) => new RichTextStylesheetData(), Map); - mapper.Define((_, _) => new RichTextRuleViewModel { Name = string.Empty, Selector = string.Empty, Styles = string.Empty }, Map); - mapper.Define((_, _) => new StylesheetOverviewResponseModel{ Name = string.Empty, Path = string.Empty }, Map); - } + mapper.Define((_, _) => new StylesheetUpdateModel { Content = string.Empty }, Map); + mapper.Define((_, _) => new StylesheetRenameModel { Name = string.Empty }, Map); - // Umbraco.Code.MapAll - private void Map(IStylesheet source, StylesheetOverviewResponseModel target, MapperContext context) - { - target.Name = source.Alias; - target.Path = source.Path; - } - - // Umbraco.Code.MapAll - private void Map(StylesheetRule source, RichTextRuleViewModel target, MapperContext context) - { - target.Name = source.Name; - target.Selector = source.Selector; - target.Styles = source.Styles; - } - - // Umbraco.Code.MapAll -Rules - private void Map(ExtractRichTextStylesheetRulesRequestModel source, RichTextStylesheetData target, MapperContext context) - => target.Content = source.Content; - - // Umbraco.Code.MapAll - private void Map(InterpolateRichTextStylesheetRequestModel source, RichTextStylesheetData target, MapperContext context) - { - target.Content = source.Content; - target.Rules = source.Rules?.Select(x => new StylesheetRule - { - Name = x.Name, Selector = x.Selector, Styles = x.Styles, - }).ToArray() ?? Array.Empty(); - } - - // Umbraco.Code.MapAll - private void Map(UpdateStylesheetRequestModel source, StylesheetUpdateModel target, MapperContext context) - { - target.Content = source.Content; - target.Name = source.Name; - target.ExistingPath = source.ExistingPath; - } - - // Umbraco.Code.MapAll - private void Map(CreateStylesheetRequestModel source, StylesheetCreateModel target, MapperContext context) - { - target.Content = source.Content; - target.Name = source.Name; - target.ParentPath = source.ParentPath; + mapper.Define((_, _) => new StylesheetFolderResponseModel { Name = string.Empty, Path = string.Empty }, Map); + mapper.Define((_, _) => new StylesheetFolderCreateModel { Name = string.Empty }, Map); } // Umbraco.Code.MapAll @@ -69,6 +28,49 @@ public class StylesheetViewModelsMapDefinition : IMapDefinition { target.Name = source.Name ?? string.Empty; target.Content = source.Content ?? string.Empty; - target.Path = source.Path; + target.Path = source.Path.SystemPathToVirtualPath(); + var parentPath = Path.GetDirectoryName(source.Path); + target.Parent = parentPath.IsNullOrWhiteSpace() + ? null + : new FileSystemFolderModel + { + Path = parentPath.SystemPathToVirtualPath() + }; + } + + // Umbraco.Code.MapAll + private void Map(CreateStylesheetRequestModel source, StylesheetCreateModel target, MapperContext context) + { + target.Content = source.Content; + target.Name = source.Name; + target.ParentPath = source.Parent?.Path.VirtualPathToSystemPath(); + } + + // Umbraco.Code.MapAll + private void Map(UpdateStylesheetRequestModel source, StylesheetUpdateModel target, MapperContext context) + => target.Content = source.Content; + + // Umbraco.Code.MapAll + private void Map(RenameStylesheetRequestModel source, StylesheetRenameModel target, MapperContext context) + => target.Name = source.Name; + + // Umbraco.Code.MapAll + private void Map(StylesheetFolderModel source, StylesheetFolderResponseModel target, MapperContext context) + { + target.Path = source.Path.SystemPathToVirtualPath(); + target.Name = source.Name; + target.Parent = source.ParentPath.IsNullOrEmpty() + ? null + : new FileSystemFolderModel + { + Path = source.ParentPath!.SystemPathToVirtualPath() + }; + } + + // Umbraco.Code.MapAll + private void Map(CreateStylesheetFolderRequestModel source, StylesheetFolderCreateModel target, MapperContext context) + { + target.Name = source.Name; + target.ParentPath = source.Parent?.Path.VirtualPathToSystemPath(); } } diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 7c8f5b06ff..f883775b00 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -334,6 +334,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -699,6 +706,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -1023,6 +1037,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -1706,6 +1727,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -2263,6 +2291,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -2693,6 +2728,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -3148,6 +3190,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -3443,6 +3492,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -4010,6 +4066,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -4274,6 +4337,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -4847,6 +4917,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -7156,6 +7233,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -7953,6 +8037,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -8203,6 +8294,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -8658,6 +8756,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -8953,6 +9058,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -9520,6 +9632,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -11199,6 +11318,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -11622,63 +11748,6 @@ } }, "/umbraco/management/api/v1/partial-view": { - "get": { - "tags": [ - "Partial View" - ], - "operationId": "GetPartialView", - "parameters": [ - { - "name": "path", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/PartialViewResponseModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/PartialViewResponseModel" - } - ] - } - }, - "text/plain": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/PartialViewResponseModel" - } - ] - } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, "post": { "tags": [ "Partial View" @@ -11719,174 +11788,13 @@ "201": { "description": "Created", "headers": { - "Location": { - "description": "Location of the newly created resource", + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", "schema": { "type": "string", - "description": "Location of the newly created resource", - "format": "uri" + "description": "Identifier of the newly created resource" } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, - "delete": { - "tags": [ - "Partial View" - ], - "operationId": "DeletePartialView", - "parameters": [ - { - "name": "path", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success" - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, - "put": { - "tags": [ - "Partial View" - ], - "operationId": "PutPartialView", - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/UpdatePartialViewRequestModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/UpdatePartialViewRequestModel" - } - ] - } - }, - "application/*+json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/UpdatePartialViewRequestModel" - } - ] - } - } - } - }, - "responses": { - "200": { - "description": "Success" - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - } - }, - "/umbraco/management/api/v1/partial-view/folder": { - "get": { - "tags": [ - "Partial View" - ], - "operationId": "GetPartialViewFolder", - "parameters": [ - { - "name": "path", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success" - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, - "post": { - "tags": [ - "Partial View" - ], - "operationId": "PostPartialViewFolder", - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/CreatePathFolderRequestModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/CreatePathFolderRequestModel" - } - ] - } - }, - "application/*+json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/CreatePathFolderRequestModel" - } - ] - } - } - } - }, - "responses": { - "201": { - "description": "Created", - "headers": { + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -11946,16 +11854,574 @@ "Backoffice User": [ ] } ] + } + }, + "/umbraco/management/api/v1/partial-view/{path}": { + "get": { + "tags": [ + "Partial View" + ], + "operationId": "GetPartialViewByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PartialViewResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PartialViewResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PartialViewResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] }, "delete": { "tags": [ "Partial View" ], - "operationId": "DeletePartialViewFolder", + "operationId": "DeletePartialViewByPath", "parameters": [ { "name": "path", - "in": "query", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + }, + "put": { + "tags": [ + "Partial View" + ], + "operationId": "PutPartialViewByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdatePartialViewRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdatePartialViewRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdatePartialViewRequestModel" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/partial-view/{path}/rename": { + "put": { + "tags": [ + "Partial View" + ], + "operationId": "PutPartialViewByPathRename", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/RenamePartialViewRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/RenamePartialViewRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/RenamePartialViewRequestModel" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, + "Location": { + "description": "Location of the newly created resource", + "schema": { + "type": "string", + "description": "Location of the newly created resource", + "format": "uri" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/partial-view/folder": { + "post": { + "tags": [ + "Partial View" + ], + "operationId": "PostPartialViewFolder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreatePartialViewFolderRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreatePartialViewFolderRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreatePartialViewFolderRequestModel" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, + "Location": { + "description": "Location of the newly created resource", + "schema": { + "type": "string", + "description": "Location of the newly created resource", + "format": "uri" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/partial-view/folder/{path}": { + "get": { + "tags": [ + "Partial View" + ], + "operationId": "GetPartialViewFolderByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PartialViewFolderResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PartialViewFolderResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PartialViewFolderResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + }, + "delete": { + "tags": [ + "Partial View" + ], + "operationId": "DeletePartialViewFolderByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, "schema": { "type": "string" } @@ -12024,7 +12490,7 @@ "operationId": "GetPartialViewItem", "parameters": [ { - "name": "id", + "name": "path", "in": "query", "schema": { "uniqueItems": true, @@ -12120,17 +12586,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PagedSnippetItemResponseModel" + "$ref": "#/components/schemas/PagedPartialViewSnippetItemResponseModel" } }, "text/json": { "schema": { - "$ref": "#/components/schemas/PagedSnippetItemResponseModel" + "$ref": "#/components/schemas/PagedPartialViewSnippetItemResponseModel" } }, "text/plain": { "schema": { - "$ref": "#/components/schemas/PagedSnippetItemResponseModel" + "$ref": "#/components/schemas/PagedPartialViewSnippetItemResponseModel" } } } @@ -12146,15 +12612,15 @@ ] } }, - "/umbraco/management/api/v1/partial-view/snippet/{name}": { + "/umbraco/management/api/v1/partial-view/snippet/{id}": { "get": { "tags": [ "Partial View" ], - "operationId": "GetPartialViewSnippetByName", + "operationId": "GetPartialViewSnippetById", "parameters": [ { - "name": "name", + "name": "id", "in": "path", "required": true, "schema": { @@ -12234,7 +12700,7 @@ "operationId": "GetTreePartialViewChildren", "parameters": [ { - "name": "path", + "name": "parentPath", "in": "query", "schema": { "type": "string" @@ -12966,6 +13432,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -13562,63 +14035,6 @@ } }, "/umbraco/management/api/v1/script": { - "get": { - "tags": [ - "Script" - ], - "operationId": "GetScript", - "parameters": [ - { - "name": "path", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/ScriptResponseModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/ScriptResponseModel" - } - ] - } - }, - "text/plain": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/ScriptResponseModel" - } - ] - } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, "post": { "tags": [ "Script" @@ -13659,174 +14075,13 @@ "201": { "description": "Created", "headers": { - "Location": { - "description": "Location of the newly created resource", + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", "schema": { "type": "string", - "description": "Location of the newly created resource", - "format": "uri" + "description": "Identifier of the newly created resource" } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, - "delete": { - "tags": [ - "Script" - ], - "operationId": "DeleteScript", - "parameters": [ - { - "name": "path", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success" - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, - "put": { - "tags": [ - "Script" - ], - "operationId": "PutScript", - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/UpdateScriptRequestModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/UpdateScriptRequestModel" - } - ] - } - }, - "application/*+json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/UpdateScriptRequestModel" - } - ] - } - } - } - }, - "responses": { - "200": { - "description": "Success" - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - } - }, - "/umbraco/management/api/v1/script/folder": { - "get": { - "tags": [ - "Script" - ], - "operationId": "GetScriptFolder", - "parameters": [ - { - "name": "path", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success" - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, - "post": { - "tags": [ - "Script" - ], - "operationId": "PostScriptFolder", - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/CreatePathFolderRequestModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/CreatePathFolderRequestModel" - } - ] - } - }, - "application/*+json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/CreatePathFolderRequestModel" - } - ] - } - } - } - }, - "responses": { - "201": { - "description": "Created", - "headers": { + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -13886,16 +14141,574 @@ "Backoffice User": [ ] } ] + } + }, + "/umbraco/management/api/v1/script/{path}": { + "get": { + "tags": [ + "Script" + ], + "operationId": "GetScriptByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ScriptResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ScriptResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ScriptResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] }, "delete": { "tags": [ "Script" ], - "operationId": "DeleteScriptFolder", + "operationId": "DeleteScriptByPath", "parameters": [ { "name": "path", - "in": "query", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + }, + "put": { + "tags": [ + "Script" + ], + "operationId": "PutScriptByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateScriptRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateScriptRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateScriptRequestModel" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/script/{path}/rename": { + "put": { + "tags": [ + "Script" + ], + "operationId": "PutScriptByPathRename", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/RenameScriptRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/RenameScriptRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/RenameScriptRequestModel" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, + "Location": { + "description": "Location of the newly created resource", + "schema": { + "type": "string", + "description": "Location of the newly created resource", + "format": "uri" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/script/folder": { + "post": { + "tags": [ + "Script" + ], + "operationId": "PostScriptFolder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateScriptFolderRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateScriptFolderRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateScriptFolderRequestModel" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, + "Location": { + "description": "Location of the newly created resource", + "schema": { + "type": "string", + "description": "Location of the newly created resource", + "format": "uri" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/script/folder/{path}": { + "get": { + "tags": [ + "Script" + ], + "operationId": "GetScriptFolderByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ScriptFolderResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ScriptFolderResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ScriptFolderResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + }, + "delete": { + "tags": [ + "Script" + ], + "operationId": "DeleteScriptFolderByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, "schema": { "type": "string" } @@ -14036,7 +14849,7 @@ "operationId": "GetTreeScriptChildren", "parameters": [ { - "name": "path", + "name": "parentPath", "in": "query", "schema": { "type": "string" @@ -14949,7 +15762,7 @@ "operationId": "GetTreeStaticFileChildren", "parameters": [ { - "name": "path", + "name": "parentPath", "in": "query", "schema": { "type": "string" @@ -15065,63 +15878,6 @@ } }, "/umbraco/management/api/v1/stylesheet": { - "get": { - "tags": [ - "Stylesheet" - ], - "operationId": "GetStylesheet", - "parameters": [ - { - "name": "path", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/StylesheetResponseModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/StylesheetResponseModel" - } - ] - } - }, - "text/plain": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/StylesheetResponseModel" - } - ] - } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, "post": { "tags": [ "Stylesheet" @@ -15162,232 +15918,13 @@ "201": { "description": "Created", "headers": { - "Location": { - "description": "Location of the newly created resource", + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", "schema": { "type": "string", - "description": "Location of the newly created resource", - "format": "uri" - } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, - "delete": { - "tags": [ - "Stylesheet" - ], - "operationId": "DeleteStylesheet", - "parameters": [ - { - "name": "path", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success" - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, - "put": { - "tags": [ - "Stylesheet" - ], - "operationId": "PutStylesheet", - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/UpdateStylesheetRequestModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/UpdateStylesheetRequestModel" - } - ] - } - }, - "application/*+json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/UpdateStylesheetRequestModel" - } - ] - } - } - } - }, - "responses": { - "200": { - "description": "Success" - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - } - }, - "/umbraco/management/api/v1/stylesheet/all": { - "get": { - "tags": [ - "Stylesheet" - ], - "operationId": "GetStylesheetAll", - "parameters": [ - { - "name": "skip", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "default": 0 - } - }, - { - "name": "take", - "in": "query", - "schema": { - "type": "integer", - "format": "int32", - "default": 100 - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PagedStylesheetOverviewResponseModel" + "description": "Identifier of the newly created resource" } }, - "text/json": { - "schema": { - "$ref": "#/components/schemas/PagedStylesheetOverviewResponseModel" - } - }, - "text/plain": { - "schema": { - "$ref": "#/components/schemas/PagedStylesheetOverviewResponseModel" - } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - } - }, - "/umbraco/management/api/v1/stylesheet/folder": { - "get": { - "tags": [ - "Stylesheet" - ], - "operationId": "GetStylesheetFolder", - "parameters": [ - { - "name": "path", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success" - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - }, - "post": { - "tags": [ - "Stylesheet" - ], - "operationId": "PostStylesheetFolder", - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/CreatePathFolderRequestModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/CreatePathFolderRequestModel" - } - ] - } - }, - "application/*+json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/CreatePathFolderRequestModel" - } - ] - } - } - } - }, - "responses": { - "201": { - "description": "Created", - "headers": { "Location": { "description": "Location of the newly created resource", "schema": { @@ -15447,16 +15984,574 @@ "Backoffice User": [ ] } ] + } + }, + "/umbraco/management/api/v1/stylesheet/{path}": { + "get": { + "tags": [ + "Stylesheet" + ], + "operationId": "GetStylesheetByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/StylesheetResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/StylesheetResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/StylesheetResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] }, "delete": { "tags": [ "Stylesheet" ], - "operationId": "DeleteStylesheetFolder", + "operationId": "DeleteStylesheetByPath", "parameters": [ { "name": "path", - "in": "query", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + }, + "put": { + "tags": [ + "Stylesheet" + ], + "operationId": "PutStylesheetByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateStylesheetRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateStylesheetRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UpdateStylesheetRequestModel" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/stylesheet/{path}/rename": { + "put": { + "tags": [ + "Stylesheet" + ], + "operationId": "PutStylesheetByPathRename", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/RenameStylesheetRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/RenameStylesheetRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/RenameStylesheetRequestModel" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, + "Location": { + "description": "Location of the newly created resource", + "schema": { + "type": "string", + "description": "Location of the newly created resource", + "format": "uri" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/stylesheet/folder": { + "post": { + "tags": [ + "Stylesheet" + ], + "operationId": "PostStylesheetFolder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateStylesheetFolderRequestModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateStylesheetFolderRequestModel" + } + ] + } + }, + "application/*+json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/CreateStylesheetFolderRequestModel" + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Created", + "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, + "Location": { + "description": "Location of the newly created resource", + "schema": { + "type": "string", + "description": "Location of the newly created resource", + "format": "uri" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + } + }, + "/umbraco/management/api/v1/stylesheet/folder/{path}": { + "get": { + "tags": [ + "Stylesheet" + ], + "operationId": "GetStylesheetFolderByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/StylesheetFolderResponseModel" + } + ] + } + }, + "text/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/StylesheetFolderResponseModel" + } + ] + } + }, + "text/plain": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/StylesheetFolderResponseModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + }, + "delete": { + "tags": [ + "Stylesheet" + ], + "operationId": "DeleteStylesheetFolderByPath", + "parameters": [ + { + "name": "path", + "in": "path", + "required": true, "schema": { "type": "string" } @@ -15546,7 +16641,7 @@ "items": { "oneOf": [ { - "$ref": "#/components/schemas/ScriptItemResponseModel" + "$ref": "#/components/schemas/StylesheetItemResponseModel" } ] } @@ -15558,7 +16653,7 @@ "items": { "oneOf": [ { - "$ref": "#/components/schemas/ScriptItemResponseModel" + "$ref": "#/components/schemas/StylesheetItemResponseModel" } ] } @@ -15570,7 +16665,7 @@ "items": { "oneOf": [ { - "$ref": "#/components/schemas/ScriptItemResponseModel" + "$ref": "#/components/schemas/StylesheetItemResponseModel" } ] } @@ -15589,236 +16684,6 @@ ] } }, - "/umbraco/management/api/v1/stylesheet/rich-text/extract-rules": { - "post": { - "tags": [ - "Stylesheet" - ], - "operationId": "PostStylesheetRichTextExtractRules", - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/ExtractRichTextStylesheetRulesRequestModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/ExtractRichTextStylesheetRulesRequestModel" - } - ] - } - }, - "application/*+json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/ExtractRichTextStylesheetRulesRequestModel" - } - ] - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/ExtractRichTextStylesheetRulesResponseModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/ExtractRichTextStylesheetRulesResponseModel" - } - ] - } - }, - "text/plain": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/ExtractRichTextStylesheetRulesResponseModel" - } - ] - } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - } - }, - "/umbraco/management/api/v1/stylesheet/rich-text/interpolate-rules": { - "post": { - "tags": [ - "Stylesheet" - ], - "operationId": "PostStylesheetRichTextInterpolateRules", - "requestBody": { - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/InterpolateRichTextStylesheetRequestModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/InterpolateRichTextStylesheetRequestModel" - } - ] - } - }, - "application/*+json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/InterpolateRichTextStylesheetRequestModel" - } - ] - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/InterpolateRichTextStylesheetResponseModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/InterpolateRichTextStylesheetResponseModel" - } - ] - } - }, - "text/plain": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/InterpolateRichTextStylesheetResponseModel" - } - ] - } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - } - }, - "/umbraco/management/api/v1/stylesheet/rich-text/rules": { - "get": { - "tags": [ - "Stylesheet" - ], - "operationId": "GetStylesheetRichTextRules", - "parameters": [ - { - "name": "path", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/RichTextStylesheetRulesResponseModel" - }, - { - "$ref": "#/components/schemas/ExtractRichTextStylesheetRulesResponseModel" - } - ] - } - }, - "text/json": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/RichTextStylesheetRulesResponseModel" - }, - { - "$ref": "#/components/schemas/ExtractRichTextStylesheetRulesResponseModel" - } - ] - } - }, - "text/plain": { - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/RichTextStylesheetRulesResponseModel" - }, - { - "$ref": "#/components/schemas/ExtractRichTextStylesheetRulesResponseModel" - } - ] - } - } - } - }, - "401": { - "description": "The resource is protected and requires an authentication token" - } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] - } - }, "/umbraco/management/api/v1/tree/stylesheet/children": { "get": { "tags": [ @@ -15827,7 +16692,7 @@ "operationId": "GetTreeStylesheetChildren", "parameters": [ { - "name": "path", + "name": "parentPath", "in": "query", "schema": { "type": "string" @@ -16240,6 +17105,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -16998,6 +17870,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -17824,6 +18703,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -19767,6 +20653,13 @@ "201": { "description": "Created", "headers": { + "Umb-Generated-Resource": { + "description": "Identifier of the newly created resource", + "schema": { + "type": "string", + "description": "Identifier of the newly created resource" + } + }, "Location": { "description": "Location of the newly created resource", "schema": { @@ -21346,28 +22239,22 @@ ], "additionalProperties": false }, - "CreatePartialViewRequestModel": { + "CreatePartialViewFolderRequestModel": { "type": "object", "allOf": [ { - "$ref": "#/components/schemas/CreateTextFileViewModelBaseModel" + "$ref": "#/components/schemas/FileSystemCreateRequestModelBaseModel" } ], "additionalProperties": false }, - "CreatePathFolderRequestModel": { + "CreatePartialViewRequestModel": { "type": "object", "allOf": [ { - "$ref": "#/components/schemas/PathFolderModelBaseModel" + "$ref": "#/components/schemas/FileSystemFileCreateRequestModelBaseModel" } ], - "properties": { - "parentPath": { - "type": "string", - "nullable": true - } - }, "additionalProperties": false }, "CreateRelationTypeRequestModel": { @@ -21386,11 +22273,29 @@ }, "additionalProperties": false }, + "CreateScriptFolderRequestModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/FileSystemCreateRequestModelBaseModel" + } + ], + "additionalProperties": false + }, "CreateScriptRequestModel": { "type": "object", "allOf": [ { - "$ref": "#/components/schemas/CreateTextFileViewModelBaseModel" + "$ref": "#/components/schemas/FileSystemFileCreateRequestModelBaseModel" + } + ], + "additionalProperties": false + }, + "CreateStylesheetFolderRequestModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/FileSystemCreateRequestModelBaseModel" } ], "additionalProperties": false @@ -21399,7 +22304,7 @@ "type": "object", "allOf": [ { - "$ref": "#/components/schemas/CreateTextFileViewModelBaseModel" + "$ref": "#/components/schemas/FileSystemFileCreateRequestModelBaseModel" } ], "additionalProperties": false @@ -21420,21 +22325,6 @@ }, "additionalProperties": false }, - "CreateTextFileViewModelBaseModel": { - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/TextFileViewModelBaseModel" - } - ], - "properties": { - "parentPath": { - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, "CreateUserGroupRequestModel": { "type": "object", "allOf": [ @@ -22527,27 +23417,6 @@ }, "additionalProperties": false }, - "ExtractRichTextStylesheetRulesRequestModel": { - "required": [ - "content" - ], - "type": "object", - "properties": { - "content": { - "type": "string" - } - }, - "additionalProperties": false - }, - "ExtractRichTextStylesheetRulesResponseModel": { - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/RichTextStylesheetRulesResponseModel" - } - ], - "additionalProperties": false - }, "FieldPresentationModel": { "required": [ "name", @@ -22567,22 +23436,171 @@ }, "additionalProperties": false }, - "FileItemResponseModelBaseModel": { + "FileSystemCreateRequestModelBaseModel": { "required": [ - "icon", - "name", - "path" + "name" ], "type": "object", "properties": { "name": { "type": "string" }, + "parent": { + "oneOf": [ + { + "$ref": "#/components/schemas/FileSystemFolderModel" + } + ], + "nullable": true + } + }, + "additionalProperties": false + }, + "FileSystemFileCreateRequestModelBaseModel": { + "required": [ + "content", + "name" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "parent": { + "oneOf": [ + { + "$ref": "#/components/schemas/FileSystemFolderModel" + } + ], + "nullable": true + }, + "content": { + "type": "string" + } + }, + "additionalProperties": false + }, + "FileSystemFileResponseModelBaseModel": { + "required": [ + "content", + "name", + "path" + ], + "type": "object", + "properties": { "path": { "type": "string" }, - "icon": { + "name": { "type": "string" + }, + "parent": { + "oneOf": [ + { + "$ref": "#/components/schemas/FileSystemFolderModel" + } + ], + "nullable": true + }, + "content": { + "type": "string" + } + }, + "additionalProperties": false + }, + "FileSystemFileUpdateRequestModelBaseModel": { + "required": [ + "content" + ], + "type": "object", + "properties": { + "content": { + "type": "string" + } + }, + "additionalProperties": false + }, + "FileSystemFolderModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/FileSystemItemViewModelBaseModel" + } + ], + "additionalProperties": false + }, + "FileSystemItemResponseModelBaseModel": { + "required": [ + "isFolder", + "name", + "path" + ], + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parent": { + "oneOf": [ + { + "$ref": "#/components/schemas/FileSystemFolderModel" + } + ], + "nullable": true + }, + "isFolder": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "FileSystemItemViewModelBaseModel": { + "required": [ + "path" + ], + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "additionalProperties": false + }, + "FileSystemRenameRequestModelBaseModel": { + "required": [ + "name" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "FileSystemResponseModelBaseModel": { + "required": [ + "name", + "path" + ], + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parent": { + "oneOf": [ + { + "$ref": "#/components/schemas/FileSystemFolderModel" + } + ], + "nullable": true } }, "additionalProperties": false @@ -22602,6 +23620,14 @@ "path": { "type": "string" }, + "parent": { + "oneOf": [ + { + "$ref": "#/components/schemas/FileSystemFolderModel" + } + ], + "nullable": true + }, "isFolder": { "type": "boolean" } @@ -22996,39 +24022,6 @@ }, "additionalProperties": false }, - "InterpolateRichTextStylesheetRequestModel": { - "type": "object", - "properties": { - "content": { - "type": "string", - "nullable": true - }, - "rules": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/RichTextRuleModel" - } - ] - }, - "nullable": true - } - }, - "additionalProperties": false - }, - "InterpolateRichTextStylesheetResponseModel": { - "required": [ - "content" - ], - "type": "object", - "properties": { - "content": { - "type": "string" - } - }, - "additionalProperties": false - }, "InviteUserRequestModel": { "type": "object", "allOf": [ @@ -24395,6 +25388,33 @@ }, "additionalProperties": false }, + "PagedPartialViewSnippetItemResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/PartialViewSnippetItemResponseModel" + }, + { + "$ref": "#/components/schemas/PartialViewSnippetResponseModel" + } + ] + } + } + }, + "additionalProperties": false + }, "PagedProblemDetailsModel": { "required": [ "items", @@ -24583,54 +25603,6 @@ }, "additionalProperties": false }, - "PagedSnippetItemResponseModel": { - "required": [ - "items", - "total" - ], - "type": "object", - "properties": { - "total": { - "type": "integer", - "format": "int64" - }, - "items": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/SnippetItemResponseModel" - } - ] - } - } - }, - "additionalProperties": false - }, - "PagedStylesheetOverviewResponseModel": { - "required": [ - "items", - "total" - ], - "type": "object", - "properties": { - "total": { - "type": "integer", - "format": "int64" - }, - "items": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/StylesheetOverviewResponseModel" - } - ] - } - } - }, - "additionalProperties": false - }, "PagedTagResponseModel": { "required": [ "items", @@ -24727,11 +25699,20 @@ }, "additionalProperties": false }, + "PartialViewFolderResponseModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/FileSystemResponseModelBaseModel" + } + ], + "additionalProperties": false + }, "PartialViewItemResponseModel": { "type": "object", "allOf": [ { - "$ref": "#/components/schemas/FileItemResponseModelBaseModel" + "$ref": "#/components/schemas/FileSystemItemResponseModelBaseModel" } ], "additionalProperties": false @@ -24740,34 +25721,42 @@ "type": "object", "allOf": [ { - "$ref": "#/components/schemas/TextFileResponseModelBaseModel" + "$ref": "#/components/schemas/FileSystemFileResponseModelBaseModel" } ], "additionalProperties": false }, - "PartialViewSnippetResponseModel": { + "PartialViewSnippetItemResponseModel": { "required": [ - "content", + "id", "name" ], "type": "object", "properties": { - "name": { + "id": { "type": "string" }, - "content": { + "name": { "type": "string" } }, "additionalProperties": false }, - "PartialViewUpdateModel": { + "PartialViewSnippetResponseModel": { + "required": [ + "content" + ], "type": "object", "allOf": [ { - "$ref": "#/components/schemas/TextFileUpdateModel" + "$ref": "#/components/schemas/PartialViewSnippetItemResponseModel" } ], + "properties": { + "content": { + "type": "string" + } + }, "additionalProperties": false }, "PasswordConfigurationResponseModel": { @@ -24799,37 +25788,6 @@ }, "additionalProperties": false }, - "PathFolderModelBaseModel": { - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/FolderModelBaseModel" - } - ], - "additionalProperties": false - }, - "PathFolderResponseModel": { - "required": [ - "path" - ], - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/FolderModelBaseModel" - } - ], - "properties": { - "parentPath": { - "type": "string", - "nullable": true - }, - "path": { - "type": "string", - "readOnly": true - } - }, - "additionalProperties": false - }, "ProblemDetails": { "type": "object", "properties": { @@ -25402,6 +26360,33 @@ }, "additionalProperties": false }, + "RenamePartialViewRequestModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/FileSystemRenameRequestModelBaseModel" + } + ], + "additionalProperties": false + }, + "RenameScriptRequestModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/FileSystemRenameRequestModelBaseModel" + } + ], + "additionalProperties": false + }, + "RenameStylesheetRequestModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/FileSystemRenameRequestModelBaseModel" + } + ], + "additionalProperties": false + }, "ResendInviteUserRequestModel": { "required": [ "userId" @@ -25450,45 +26435,6 @@ }, "additionalProperties": false }, - "RichTextRuleModel": { - "required": [ - "name", - "selector", - "styles" - ], - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "selector": { - "type": "string" - }, - "styles": { - "type": "string" - } - }, - "additionalProperties": false - }, - "RichTextStylesheetRulesResponseModel": { - "required": [ - "rules" - ], - "type": "object", - "properties": { - "rules": { - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/RichTextRuleModel" - } - ] - } - } - }, - "additionalProperties": false - }, "RuntimeLevelModel": { "enum": [ "Unknown", @@ -25542,11 +26488,20 @@ ], "additionalProperties": false }, + "ScriptFolderResponseModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/FileSystemResponseModelBaseModel" + } + ], + "additionalProperties": false + }, "ScriptItemResponseModel": { "type": "object", "allOf": [ { - "$ref": "#/components/schemas/FileItemResponseModelBaseModel" + "$ref": "#/components/schemas/FileSystemItemResponseModelBaseModel" } ], "additionalProperties": false @@ -25555,25 +26510,7 @@ "type": "object", "allOf": [ { - "$ref": "#/components/schemas/TextFileResponseModelBaseModel" - } - ], - "additionalProperties": false - }, - "ScriptUpdateModel": { - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/TextFileUpdateModel" - } - ], - "additionalProperties": false - }, - "ScriptViewModelBaseModel": { - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/TextFileViewModelBaseModel" + "$ref": "#/components/schemas/FileSystemFileResponseModelBaseModel" } ], "additionalProperties": false @@ -25756,18 +26693,6 @@ ], "additionalProperties": false }, - "SnippetItemResponseModel": { - "required": [ - "name" - ], - "type": "object", - "properties": { - "name": { - "type": "string" - } - }, - "additionalProperties": false - }, "SortingRequestModel": { "required": [ "sorting" @@ -25796,7 +26721,7 @@ "type": "object", "allOf": [ { - "$ref": "#/components/schemas/FileItemResponseModelBaseModel" + "$ref": "#/components/schemas/FileSystemItemResponseModelBaseModel" } ], "additionalProperties": false @@ -25810,45 +26735,29 @@ ], "type": "string" }, - "StylesheetItemResponseModel": { + "StylesheetFolderResponseModel": { "type": "object", "allOf": [ { - "$ref": "#/components/schemas/FileItemResponseModelBaseModel" + "$ref": "#/components/schemas/FileSystemResponseModelBaseModel" } ], "additionalProperties": false }, - "StylesheetOverviewResponseModel": { - "required": [ - "name", - "path" - ], + "StylesheetItemResponseModel": { "type": "object", - "properties": { - "name": { - "type": "string" - }, - "path": { - "type": "string" + "allOf": [ + { + "$ref": "#/components/schemas/FileSystemItemResponseModelBaseModel" } - }, + ], "additionalProperties": false }, "StylesheetResponseModel": { "type": "object", "allOf": [ { - "$ref": "#/components/schemas/TextFileResponseModelBaseModel" - } - ], - "additionalProperties": false - }, - "StylesheetUpdateModel": { - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/TextFileUpdateModel" + "$ref": "#/components/schemas/FileSystemFileResponseModelBaseModel" } ], "additionalProperties": false @@ -26223,60 +27132,6 @@ }, "additionalProperties": false }, - "TextFileResponseModelBaseModel": { - "required": [ - "path" - ], - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/TextFileViewModelBaseModel" - } - ], - "properties": { - "path": { - "minLength": 1, - "type": "string" - } - }, - "additionalProperties": false - }, - "TextFileUpdateModel": { - "required": [ - "content", - "existingPath", - "name" - ], - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "content": { - "type": "string" - }, - "existingPath": { - "type": "string" - } - }, - "additionalProperties": false - }, - "TextFileViewModelBaseModel": { - "required": [ - "content", - "name" - ], - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "content": { - "type": "string" - } - }, - "additionalProperties": false - }, "TourStatusModel": { "required": [ "alias", @@ -26763,7 +27618,7 @@ "type": "object", "allOf": [ { - "$ref": "#/components/schemas/TextFileUpdateModel" + "$ref": "#/components/schemas/FileSystemFileUpdateRequestModelBaseModel" } ], "additionalProperties": false @@ -26781,7 +27636,7 @@ "type": "object", "allOf": [ { - "$ref": "#/components/schemas/UpdateTextFileViewModelBaseModel" + "$ref": "#/components/schemas/FileSystemFileUpdateRequestModelBaseModel" } ], "additionalProperties": false @@ -26790,7 +27645,7 @@ "type": "object", "allOf": [ { - "$ref": "#/components/schemas/UpdateTextFileViewModelBaseModel" + "$ref": "#/components/schemas/FileSystemFileUpdateRequestModelBaseModel" } ], "additionalProperties": false @@ -26804,23 +27659,6 @@ ], "additionalProperties": false }, - "UpdateTextFileViewModelBaseModel": { - "required": [ - "existingPath" - ], - "type": "object", - "allOf": [ - { - "$ref": "#/components/schemas/TextFileViewModelBaseModel" - } - ], - "properties": { - "existingPath": { - "type": "string" - } - }, - "additionalProperties": false - }, "UpdateUserGroupRequestModel": { "type": "object", "allOf": [ diff --git a/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs b/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs index 2346800b74..e579fe2ce9 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs +++ b/src/Umbraco.Cms.Api.Management/OpenApi/ReponseHeaderOperationFilter.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using Umbraco.Cms.Api.Management.DependencyInjection; +using Umbraco.Cms.Core; using Umbraco.Extensions; namespace Umbraco.Cms.Api.Management.OpenApi; @@ -20,13 +21,15 @@ internal class ResponseHeaderOperationFilter : IOperationFilter switch (int.Parse(key)) { case StatusCodes.Status201Created: - SetHeader(value, "Location", "Location of the newly created resource", "string", "uri"); + // NOTE: The header order matters to the back-office client. Do not change. + SetHeader(value, Constants.Headers.GeneratedResource, "Identifier of the newly created resource", "string"); + SetHeader(value, Constants.Headers.Location, "Location of the newly created resource", "string", "uri"); break; } } } - private void SetHeader(OpenApiResponse value, string headerName, string description, string type, string format) + private void SetHeader(OpenApiResponse value, string headerName, string description, string type, string? format = null) { if (value.Headers is null) diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemCreateRequestModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemCreateRequestModelBase.cs new file mode 100644 index 0000000000..56676a2682 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemCreateRequestModelBase.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +public abstract class FileSystemCreateRequestModelBase +{ + public required string Name { get; set; } + + public FileSystemFolderModel? Parent { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileCreateRequestModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileCreateRequestModelBase.cs new file mode 100644 index 0000000000..33df366e52 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileCreateRequestModelBase.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +public abstract class FileSystemFileCreateRequestModelBase : FileSystemCreateRequestModelBase +{ + public required string Content { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileResponseModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileResponseModelBase.cs new file mode 100644 index 0000000000..4825e1c846 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileResponseModelBase.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +public abstract class FileSystemFileResponseModelBase : FileSystemResponseModelBase +{ + public required string Content { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileUpdateRequestModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileUpdateRequestModelBase.cs new file mode 100644 index 0000000000..59e0f4a9d9 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFileUpdateRequestModelBase.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +public abstract class FileSystemFileUpdateRequestModelBase +{ + public required string Content { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFolderModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFolderModel.cs new file mode 100644 index 0000000000..65eb741385 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemFolderModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +public class FileSystemFolderModel : FileSystemItemViewModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemItemResponseModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemItemResponseModelBase.cs new file mode 100644 index 0000000000..3522de1738 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemItemResponseModelBase.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +public abstract class FileSystemItemResponseModelBase : FileSystemResponseModelBase +{ + public required bool IsFolder { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemItemViewModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemItemViewModelBase.cs new file mode 100644 index 0000000000..9c3d69e1e6 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemItemViewModelBase.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +public abstract class FileSystemItemViewModelBase +{ + public required string Path { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemRenameRequestModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemRenameRequestModelBase.cs new file mode 100644 index 0000000000..eee7baf745 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemRenameRequestModelBase.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +public abstract class FileSystemRenameRequestModelBase +{ + public required string Name { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemResponseModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemResponseModelBase.cs new file mode 100644 index 0000000000..c004de795a --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/FileSystem/FileSystemResponseModelBase.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +public abstract class FileSystemResponseModelBase : FileSystemItemViewModelBase +{ + public required string Name { get; set; } + + public FileSystemFolderModel? Parent { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/CreatePathFolderRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Folder/CreatePathFolderRequestModel.cs deleted file mode 100644 index b1d180fe7b..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/CreatePathFolderRequestModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.Folder; - -public class CreatePathFolderRequestModel : PathFolderModelBase -{ - public string? ParentPath { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/PathFolderModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Folder/PathFolderModelBase.cs deleted file mode 100644 index 6ffd78a5de..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/PathFolderModelBase.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.Folder; - -public class PathFolderModelBase : FolderModelBase -{ -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/PathFolderResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Folder/PathFolderResponseModel.cs deleted file mode 100644 index 12d7993ea0..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/PathFolderResponseModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.Folder; - -public class PathFolderResponseModel : FolderModelBase -{ - public string? ParentPath { get; set; } - - public string Path => - string.IsNullOrEmpty(ParentPath) - ? Name - : System.IO.Path.Combine(ParentPath, Name); -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Item/FileItemResponseModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Item/FileItemResponseModelBase.cs deleted file mode 100644 index 7c870803fd..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Item/FileItemResponseModelBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.Item; - -public class FileItemResponseModelBase -{ - public string Name { get; set; } = string.Empty; - - public string Path { get; set; } = string.Empty; - - public string Icon { get; set; } = string.Empty; -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Item/SnippetItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Item/SnippetItemResponseModel.cs deleted file mode 100644 index 997597051f..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Item/SnippetItemResponseModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.Item; - -public class SnippetItemResponseModel -{ - public required string Name { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/CreatePartialViewRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/CreatePartialViewRequestModel.cs index 6400ca758b..5a9c673994 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/CreatePartialViewRequestModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/CreatePartialViewRequestModel.cs @@ -1,8 +1,8 @@ -using Umbraco.Cms.Api.Management.ViewModels.TextFiles; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.PartialView; -public class CreatePartialViewRequestModel : CreateTextFileViewModelBase +public class CreatePartialViewRequestModel : FileSystemFileCreateRequestModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Folder/CreatePartialViewFolderRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Folder/CreatePartialViewFolderRequestModel.cs new file mode 100644 index 0000000000..3ecac4d778 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Folder/CreatePartialViewFolderRequestModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +namespace Umbraco.Cms.Api.Management.ViewModels.PartialView.Folder; + +public class CreatePartialViewFolderRequestModel : FileSystemCreateRequestModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Folder/PartialViewFolderResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Folder/PartialViewFolderResponseModel.cs new file mode 100644 index 0000000000..c52d0c4913 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Folder/PartialViewFolderResponseModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +namespace Umbraco.Cms.Api.Management.ViewModels.PartialView.Folder; + +public class PartialViewFolderResponseModel : FileSystemResponseModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Item/PartialViewItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Item/PartialViewItemResponseModel.cs index a4cd40ca62..fd2786e92e 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Item/PartialViewItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Item/PartialViewItemResponseModel.cs @@ -1,7 +1,7 @@ -using Umbraco.Cms.Api.Management.ViewModels.Item; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.PartialView.Item; -public class PartialViewItemResponseModel : FileItemResponseModelBase +public class PartialViewItemResponseModel : FileSystemItemResponseModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/PartialViewResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/PartialViewResponseModel.cs index 410a6af708..93d6af4b98 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/PartialViewResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/PartialViewResponseModel.cs @@ -1,7 +1,7 @@ -using Umbraco.Cms.Api.Management.ViewModels.TextFiles; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.PartialView; -public class PartialViewResponseModel : TextFileResponseModelBase +public class PartialViewResponseModel : FileSystemFileResponseModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/RenamePartialViewRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/RenamePartialViewRequestModel.cs new file mode 100644 index 0000000000..3ec4194869 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/RenamePartialViewRequestModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +namespace Umbraco.Cms.Api.Management.ViewModels.PartialView; + +public class RenamePartialViewRequestModel : FileSystemRenameRequestModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/PartialViewSnippetItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/PartialViewSnippetItemResponseModel.cs new file mode 100644 index 0000000000..ba80a37355 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/PartialViewSnippetItemResponseModel.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.PartialView.Snippets; + +public class PartialViewSnippetItemResponseModel +{ + public required string Id { get; set; } + + public required string Name { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/PartialViewSnippetsViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/PartialViewSnippetResponseModel.cs similarity index 57% rename from src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/PartialViewSnippetsViewModel.cs rename to src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/PartialViewSnippetResponseModel.cs index e8faa90f7e..19cf3d9642 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/PartialViewSnippetsViewModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/PartialViewSnippetResponseModel.cs @@ -1,8 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.PartialView.Snippets; -public class PartialViewSnippetResponseModel +public class PartialViewSnippetResponseModel : PartialViewSnippetItemResponseModel { - public required string Name { get; set; } - public required string Content { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/SnippetItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/SnippetItemResponseModel.cs deleted file mode 100644 index f3e20bd179..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/Snippets/SnippetItemResponseModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Umbraco.Cms.Api.Management.ViewModels.PartialView.Snippets; - -public class SnippetItemResponseModel -{ - public SnippetItemResponseModel() - { - - } - - [SetsRequiredMembers] - public SnippetItemResponseModel(string name) - { - Name = name; - } - - public required string Name { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/UpdatePartialViewRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/UpdatePartialViewRequestModel.cs index 4c42ebe0bf..ab4c7c90d8 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/UpdatePartialViewRequestModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/PartialView/UpdatePartialViewRequestModel.cs @@ -1,7 +1,7 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.PartialView; -public class UpdatePartialViewRequestModel : TextFileUpdateModel +public class UpdatePartialViewRequestModel : FileSystemFileUpdateRequestModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/ExtractRichTextStylesheetRulesRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/ExtractRichTextStylesheetRulesRequestModel.cs deleted file mode 100644 index 47068d613d..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/ExtractRichTextStylesheetRulesRequestModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.RichTextStylesheet; - -public class ExtractRichTextStylesheetRulesRequestModel -{ - public required string Content { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/ExtractRichTextStylesheetRulesResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/ExtractRichTextStylesheetRulesResponseModel.cs deleted file mode 100644 index 0444544e5d..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/ExtractRichTextStylesheetRulesResponseModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.RichTextStylesheet; - -public class ExtractRichTextStylesheetRulesResponseModel : RichTextStylesheetRulesResponseModel -{ - -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/InterpolateRichTextStylesheetRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/InterpolateRichTextStylesheetRequestModel.cs deleted file mode 100644 index b9831cd605..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/InterpolateRichTextStylesheetRequestModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.RichTextStylesheet; - -public class InterpolateRichTextStylesheetRequestModel -{ - public string? Content { get; set; } - - public IEnumerable? Rules { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/InterpolateRichTextStylesheetResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/InterpolateRichTextStylesheetResponseModel.cs deleted file mode 100644 index b0a18d652d..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/InterpolateRichTextStylesheetResponseModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.RichTextStylesheet; - -public class InterpolateRichTextStylesheetResponseModel -{ - public required string Content { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/RichTextRuleViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/RichTextRuleViewModel.cs deleted file mode 100644 index c23b12c199..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/RichTextRuleViewModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.RichTextStylesheet; - -public class RichTextRuleViewModel -{ - public required string Name { get; set; } - - public required string Selector { get; set; } - - public required string Styles { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/RichTextStylesheetRulesResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/RichTextStylesheetRulesResponseModel.cs deleted file mode 100644 index 01cd01d8ca..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/RichTextStylesheet/RichTextStylesheetRulesResponseModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.RichTextStylesheet; - -public class RichTextStylesheetRulesResponseModel -{ - public required IEnumerable Rules { get; set; } = Enumerable.Empty(); -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Script/CreateScriptRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Script/CreateScriptRequestModel.cs index 86d1ddbfb3..451454e177 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Script/CreateScriptRequestModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Script/CreateScriptRequestModel.cs @@ -1,8 +1,8 @@ -using Umbraco.Cms.Api.Management.ViewModels.TextFiles; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.Script; -public class CreateScriptRequestModel : CreateTextFileViewModelBase +public class CreateScriptRequestModel : FileSystemFileCreateRequestModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Script/Folder/CreateScriptFolderRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Script/Folder/CreateScriptFolderRequestModel.cs new file mode 100644 index 0000000000..31863b48ae --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Script/Folder/CreateScriptFolderRequestModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +namespace Umbraco.Cms.Api.Management.ViewModels.Script.Folder; + +public class CreateScriptFolderRequestModel : FileSystemCreateRequestModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Script/Folder/ScriptFolderResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Script/Folder/ScriptFolderResponseModel.cs new file mode 100644 index 0000000000..7ccc0e8cc3 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Script/Folder/ScriptFolderResponseModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +namespace Umbraco.Cms.Api.Management.ViewModels.Script.Folder; + +public class ScriptFolderResponseModel : FileSystemResponseModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Script/Item/ScriptItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Script/Item/ScriptItemResponseModel.cs index 21ebb71a6f..4985cd9e4c 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Script/Item/ScriptItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Script/Item/ScriptItemResponseModel.cs @@ -1,8 +1,8 @@ -using Umbraco.Cms.Api.Management.ViewModels.Item; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.Script.Item; -public class ScriptItemResponseModel : FileItemResponseModelBase +public class ScriptItemResponseModel : FileSystemItemResponseModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Script/RenameScriptRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Script/RenameScriptRequestModel.cs new file mode 100644 index 0000000000..f14a55370d --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Script/RenameScriptRequestModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +namespace Umbraco.Cms.Api.Management.ViewModels.Script; + +public class RenameScriptRequestModel : FileSystemRenameRequestModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Script/ScriptResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Script/ScriptResponseModel.cs index d200b36908..58ea5cbaa7 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Script/ScriptResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Script/ScriptResponseModel.cs @@ -1,7 +1,7 @@ -using Umbraco.Cms.Api.Management.ViewModels.TextFiles; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.Script; -public class ScriptResponseModel : TextFileResponseModelBase +public class ScriptResponseModel : FileSystemFileResponseModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Script/ScriptViewModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Script/ScriptViewModelBase.cs deleted file mode 100644 index 32bebb8d5f..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Script/ScriptViewModelBase.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Umbraco.Cms.Api.Management.ViewModels.TextFiles; - -namespace Umbraco.Cms.Api.Management.ViewModels.Script; - -public class ScriptViewModelBase : TextFileViewModelBase -{ -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Script/UpdateScriptRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Script/UpdateScriptRequestModel.cs index 8ff724bf37..efae73a915 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Script/UpdateScriptRequestModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Script/UpdateScriptRequestModel.cs @@ -1,7 +1,7 @@ -using Umbraco.Cms.Api.Management.ViewModels.TextFiles; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.Script; -public class UpdateScriptRequestModel : UpdateTextFileViewModelBase +public class UpdateScriptRequestModel : FileSystemFileUpdateRequestModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/StaticFile/Item/StaticFileItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/StaticFile/Item/StaticFileItemResponseModel.cs index 1082c809cc..e5c536a542 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/StaticFile/Item/StaticFileItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/StaticFile/Item/StaticFileItemResponseModel.cs @@ -1,7 +1,7 @@ -using Umbraco.Cms.Api.Management.ViewModels.Item; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.StaticFile.Item; -public class StaticFileItemResponseModel : FileItemResponseModelBase +public class StaticFileItemResponseModel : FileSystemItemResponseModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/CreateStylesheetRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/CreateStylesheetRequestModel.cs index 59c459f644..6f443dfd47 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/CreateStylesheetRequestModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/CreateStylesheetRequestModel.cs @@ -1,8 +1,8 @@ -using Umbraco.Cms.Api.Management.ViewModels.TextFiles; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.Stylesheet; -public class CreateStylesheetRequestModel : CreateTextFileViewModelBase +public class CreateStylesheetRequestModel : FileSystemFileCreateRequestModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Folder/CreateStylesheetFolderRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Folder/CreateStylesheetFolderRequestModel.cs new file mode 100644 index 0000000000..1de22e6ad5 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Folder/CreateStylesheetFolderRequestModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +namespace Umbraco.Cms.Api.Management.ViewModels.Stylesheet.Folder; + +public class CreateStylesheetFolderRequestModel : FileSystemCreateRequestModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Folder/StylesheetFolderResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Folder/StylesheetFolderResponseModel.cs new file mode 100644 index 0000000000..48d0b37034 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Folder/StylesheetFolderResponseModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +namespace Umbraco.Cms.Api.Management.ViewModels.Stylesheet.Folder; + +public class StylesheetFolderResponseModel : FileSystemResponseModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Item/StylesheetItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Item/StylesheetItemResponseModel.cs index feaf8dbf2c..91b039134b 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Item/StylesheetItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/Item/StylesheetItemResponseModel.cs @@ -1,7 +1,7 @@ -using Umbraco.Cms.Api.Management.ViewModels.Item; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.Stylesheet.Item; -public class StylesheetItemResponseModel : FileItemResponseModelBase +public class StylesheetItemResponseModel : FileSystemItemResponseModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/RenameStylesheetRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/RenameStylesheetRequestModel.cs new file mode 100644 index 0000000000..6b99aa0c67 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/RenameStylesheetRequestModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +namespace Umbraco.Cms.Api.Management.ViewModels.Stylesheet; + +public class RenameStylesheetRequestModel : FileSystemRenameRequestModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/StylesheetOverviewResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/StylesheetOverviewResponseModel.cs deleted file mode 100644 index cd30066c8f..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/StylesheetOverviewResponseModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.Stylesheet; - -public class StylesheetOverviewResponseModel -{ - public required string Name { get; set; } - - public required string Path { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/StylesheetResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/StylesheetResponseModel.cs index 908423a210..56767968ad 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/StylesheetResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/StylesheetResponseModel.cs @@ -1,7 +1,7 @@ -using Umbraco.Cms.Api.Management.ViewModels.TextFiles; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.Stylesheet; -public class StylesheetResponseModel : TextFileResponseModelBase +public class StylesheetResponseModel : FileSystemFileResponseModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/UpdateStylesheetRequestModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/UpdateStylesheetRequestModel.cs index 117ea48af5..de04905434 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/UpdateStylesheetRequestModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Stylesheet/UpdateStylesheetRequestModel.cs @@ -1,7 +1,7 @@ -using Umbraco.Cms.Api.Management.ViewModels.TextFiles; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; namespace Umbraco.Cms.Api.Management.ViewModels.Stylesheet; -public class UpdateStylesheetRequestModel : UpdateTextFileViewModelBase +public class UpdateStylesheetRequestModel : FileSystemFileUpdateRequestModelBase { } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/CreateTextFileViewModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/CreateTextFileViewModelBase.cs deleted file mode 100644 index 53b50f9a95..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/CreateTextFileViewModelBase.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.TextFiles; - -public class CreateTextFileViewModelBase : TextFileViewModelBase -{ - public string? ParentPath { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/TextFileResponseModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/TextFileResponseModelBase.cs deleted file mode 100644 index 56f65afdff..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/TextFileResponseModelBase.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Umbraco.Cms.Api.Management.ViewModels.TextFiles; - -public class TextFileResponseModelBase : TextFileViewModelBase -{ - [Required] - public string Path { get; set; } = string.Empty; -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/TextFileViewModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/TextFileViewModelBase.cs deleted file mode 100644 index 708950a8f7..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/TextFileViewModelBase.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.TextFiles; - -public class TextFileViewModelBase -{ - public required string Name { get; set; } - - public required string Content { get; set; } -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/UpdateTextFileViewModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/UpdateTextFileViewModelBase.cs deleted file mode 100644 index 28871ad6f6..0000000000 --- a/src/Umbraco.Cms.Api.Management/ViewModels/TextFiles/UpdateTextFileViewModelBase.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.TextFiles; - -public class UpdateTextFileViewModelBase : TextFileViewModelBase -{ - public required string ExistingPath { get; set; } - -} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/FileSystemTreeItemPresentationModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/FileSystemTreeItemPresentationModel.cs index 93168bf80a..b6cc6519ce 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/FileSystemTreeItemPresentationModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/FileSystemTreeItemPresentationModel.cs @@ -1,8 +1,12 @@ -namespace Umbraco.Cms.Api.Management.ViewModels.Tree; +using Umbraco.Cms.Api.Management.ViewModels.FileSystem; + +namespace Umbraco.Cms.Api.Management.ViewModels.Tree; public class FileSystemTreeItemPresentationModel : TreeItemPresentationModel { public string Path { get; set; } = string.Empty; + public FileSystemFolderModel? Parent { get; set; } + public bool IsFolder { get; set; } } diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml index f8a29e123b..27849ee098 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml @@ -71,6 +71,7 @@ @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/entity-action", backofficeAssetsPath + "/packages/core/entity-action/index.js")), @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/entity-bulk-action", backofficeAssetsPath + "/packages/core/entity-bulk-action/index.js")), @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/extension-registry", backofficeAssetsPath + "/packages/core/extension-registry/index.js")), + @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/server-file-system", backofficeAssetsPath + "/packages/core/server-file-system/index.js")), @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/id", backofficeAssetsPath + "/packages/core/id/index.js")), @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/localization", backofficeAssetsPath + "/packages/core/localization/index.js")), @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/macro", backofficeAssetsPath + "/packages/core/macro/index.js")), diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml index f8a29e123b..27849ee098 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoInstall/Index.cshtml @@ -71,6 +71,7 @@ @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/entity-action", backofficeAssetsPath + "/packages/core/entity-action/index.js")), @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/entity-bulk-action", backofficeAssetsPath + "/packages/core/entity-bulk-action/index.js")), @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/extension-registry", backofficeAssetsPath + "/packages/core/extension-registry/index.js")), + @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/server-file-system", backofficeAssetsPath + "/packages/core/server-file-system/index.js")), @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/id", backofficeAssetsPath + "/packages/core/id/index.js")), @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/localization", backofficeAssetsPath + "/packages/core/localization/index.js")), @Html.Raw(ImportMapValue("@umbraco-cms/backoffice/macro", backofficeAssetsPath + "/packages/core/macro/index.js")), diff --git a/src/Umbraco.Core/Constants-Headers.cs b/src/Umbraco.Core/Constants-Headers.cs new file mode 100644 index 0000000000..c4aed6c301 --- /dev/null +++ b/src/Umbraco.Core/Constants-Headers.cs @@ -0,0 +1,17 @@ +namespace Umbraco.Cms.Core; + +public static partial class Constants +{ + public static class Headers + { + /// + /// Location header name. + /// + public const string Location = "Location"; + + /// + /// Generated resource identifier header name. + /// + public const string GeneratedResource = "Umb-Generated-Resource"; + } +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs index f6ba0ca03f..66415f88ee 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Collections.cs @@ -110,7 +110,6 @@ public static partial class UmbracoBuilderExtensions .Add() .Add(builder.TypeLoader.GetTypes()); builder.PartialViewSnippets(); - builder.PartialViewMacroSnippets(); builder.DataValueReferenceFactories(); builder.PropertyValueConverters().Append(builder.TypeLoader.GetTypes()); builder.UrlSegmentProviders().Append(); @@ -245,13 +244,6 @@ public static partial class UmbracoBuilderExtensions public static PartialViewSnippetCollectionBuilder PartialViewSnippets(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); - /// - /// Gets the partial view macro snippets collection builder. - /// - /// The builder. - public static PartialViewMacroSnippetCollectionBuilder PartialViewMacroSnippets(this IUmbracoBuilder builder) - => builder.WithCollectionBuilder(); - /// /// Gets the cache refreshers collection builder. /// diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 9347994407..414d5035d2 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -37,6 +37,7 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.ContentTypeEditing; using Umbraco.Cms.Core.DynamicRoot; +using Umbraco.Cms.Core.Services.FileSystem; using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Telemetry; using Umbraco.Cms.Core.Templates; @@ -320,7 +321,6 @@ namespace Umbraco.Cms.Core.DependencyInjection Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); - Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); Services.AddUnique(); diff --git a/src/Umbraco.Core/Models/FileSystem/FolderCreateModel.cs b/src/Umbraco.Core/Models/FileSystem/FolderCreateModel.cs new file mode 100644 index 0000000000..dbff5aa46b --- /dev/null +++ b/src/Umbraco.Core/Models/FileSystem/FolderCreateModel.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Core.Models.FileSystem; + +public abstract class FolderCreateModel +{ + public required string Name { get; set; } + + public string? ParentPath { get; set; } +} diff --git a/src/Umbraco.Core/Models/FileSystem/FolderModelBase.cs b/src/Umbraco.Core/Models/FileSystem/FolderModelBase.cs new file mode 100644 index 0000000000..2e0e9b88a0 --- /dev/null +++ b/src/Umbraco.Core/Models/FileSystem/FolderModelBase.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Cms.Core.Models.FileSystem; + +public abstract class FolderModelBase +{ + public string Name { get; set; } = string.Empty; + + public string Path { get; set; } = string.Empty; + + public string? ParentPath { get; set; } +} diff --git a/src/Umbraco.Core/Models/FileSystem/PartialViewFolderCreateModel.cs b/src/Umbraco.Core/Models/FileSystem/PartialViewFolderCreateModel.cs new file mode 100644 index 0000000000..f3eef5f157 --- /dev/null +++ b/src/Umbraco.Core/Models/FileSystem/PartialViewFolderCreateModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.Models.FileSystem; + +public class PartialViewFolderCreateModel : FolderCreateModel +{ +} diff --git a/src/Umbraco.Core/Models/FileSystem/PartialViewFolderModel.cs b/src/Umbraco.Core/Models/FileSystem/PartialViewFolderModel.cs new file mode 100644 index 0000000000..bb3750e552 --- /dev/null +++ b/src/Umbraco.Core/Models/FileSystem/PartialViewFolderModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.Models.FileSystem; + +public class PartialViewFolderModel : FolderModelBase +{ +} diff --git a/src/Umbraco.Core/Models/FileSystem/ScriptFolderCreateModel.cs b/src/Umbraco.Core/Models/FileSystem/ScriptFolderCreateModel.cs new file mode 100644 index 0000000000..437851b191 --- /dev/null +++ b/src/Umbraco.Core/Models/FileSystem/ScriptFolderCreateModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.Models.FileSystem; + +public class ScriptFolderCreateModel : FolderCreateModel +{ +} diff --git a/src/Umbraco.Core/Models/FileSystem/ScriptFolderModel.cs b/src/Umbraco.Core/Models/FileSystem/ScriptFolderModel.cs new file mode 100644 index 0000000000..8e0b669551 --- /dev/null +++ b/src/Umbraco.Core/Models/FileSystem/ScriptFolderModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.Models.FileSystem; + +public class ScriptFolderModel : FolderModelBase +{ +} diff --git a/src/Umbraco.Core/Models/FileSystem/StylesheetFolderCreateModel.cs b/src/Umbraco.Core/Models/FileSystem/StylesheetFolderCreateModel.cs new file mode 100644 index 0000000000..3051f93256 --- /dev/null +++ b/src/Umbraco.Core/Models/FileSystem/StylesheetFolderCreateModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.Models.FileSystem; + +public class StylesheetFolderCreateModel : FolderCreateModel +{ +} diff --git a/src/Umbraco.Core/Models/FileSystem/StylesheetFolderModel.cs b/src/Umbraco.Core/Models/FileSystem/StylesheetFolderModel.cs new file mode 100644 index 0000000000..bd954767d2 --- /dev/null +++ b/src/Umbraco.Core/Models/FileSystem/StylesheetFolderModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.Models.FileSystem; + +public class StylesheetFolderModel : FolderModelBase +{ +} diff --git a/src/Umbraco.Core/Models/PartialViewRenameModel.cs b/src/Umbraco.Core/Models/PartialViewRenameModel.cs new file mode 100644 index 0000000000..da339e40c4 --- /dev/null +++ b/src/Umbraco.Core/Models/PartialViewRenameModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.Models; + +public class PartialViewRenameModel : TextFileRenameModel +{ +} diff --git a/src/Umbraco.Core/Models/PathContainer.cs b/src/Umbraco.Core/Models/PathContainer.cs deleted file mode 100644 index 8a9a857077..0000000000 --- a/src/Umbraco.Core/Models/PathContainer.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Umbraco.Cms.Core.Models; - -/// -/// A container using a path as its identity. -/// -public class PathContainer -{ - /// - /// The name of the container - /// - public required string Name { get; set; } - - /// - /// The path to the parent of the container - /// - public string? ParentPath { get; set; } - - /// - /// The path of the container. - /// - public string Path => - string.IsNullOrEmpty(ParentPath) - ? Name - : System.IO.Path.Combine(ParentPath, Name); -} diff --git a/src/Umbraco.Core/Models/ScriptRenameModel.cs b/src/Umbraco.Core/Models/ScriptRenameModel.cs new file mode 100644 index 0000000000..896b3c8eec --- /dev/null +++ b/src/Umbraco.Core/Models/ScriptRenameModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.Models; + +public class ScriptRenameModel : TextFileRenameModel +{ +} diff --git a/src/Umbraco.Core/Models/StylesheetRenameModel.cs b/src/Umbraco.Core/Models/StylesheetRenameModel.cs new file mode 100644 index 0000000000..9dde53046c --- /dev/null +++ b/src/Umbraco.Core/Models/StylesheetRenameModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Core.Models; + +public class StylesheetRenameModel : TextFileRenameModel +{ +} diff --git a/src/Umbraco.Core/Models/TextFileCreateModel.cs b/src/Umbraco.Core/Models/TextFileCreateModel.cs index 8d05f6aa62..0b434cb703 100644 --- a/src/Umbraco.Core/Models/TextFileCreateModel.cs +++ b/src/Umbraco.Core/Models/TextFileCreateModel.cs @@ -1,15 +1,10 @@ namespace Umbraco.Cms.Core.Models; -public class TextFileCreateModel +public abstract class TextFileCreateModel { public required string Name { get; set; } public string? ParentPath { get; set; } public string? Content { get; set; } - - public string FilePath => - ParentPath is null - ? Name - : Path.Combine(ParentPath, Name); } diff --git a/src/Umbraco.Core/Models/TextFileRenameModel.cs b/src/Umbraco.Core/Models/TextFileRenameModel.cs new file mode 100644 index 0000000000..f60d77bb73 --- /dev/null +++ b/src/Umbraco.Core/Models/TextFileRenameModel.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Cms.Core.Models; + +public abstract class TextFileRenameModel +{ + /// + /// The new name of the file. + /// + public required string Name { get; set; } +} diff --git a/src/Umbraco.Core/Models/TextFileUpdateModel.cs b/src/Umbraco.Core/Models/TextFileUpdateModel.cs index 2b9c07aa87..20cb85339f 100644 --- a/src/Umbraco.Core/Models/TextFileUpdateModel.cs +++ b/src/Umbraco.Core/Models/TextFileUpdateModel.cs @@ -1,19 +1,9 @@ namespace Umbraco.Cms.Core.Models; -public class TextFileUpdateModel +public abstract class TextFileUpdateModel { - /// - /// The new name of the file. - /// - public required string Name { get; set; } - /// /// The new content of the file. /// public required string Content { get; set; } - - /// - /// The path of the file to update. - /// - public required string ExistingPath { get; set; } } diff --git a/src/Umbraco.Core/Services/FileServiceOperationBase.cs b/src/Umbraco.Core/Services/FileServiceOperationBase.cs new file mode 100644 index 0000000000..703465a1b3 --- /dev/null +++ b/src/Umbraco.Core/Services/FileServiceOperationBase.cs @@ -0,0 +1,258 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Persistence; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Services; + +public abstract class FileServiceOperationBase : FileServiceBase + where TRepository : IFileRepository, IReadRepository, IWriteRepository, IFileWithFoldersRepository + where TEntity : IFile + where TOperationStatus : Enum +{ + private readonly ILogger _logger; + private readonly IUserIdKeyResolver _userIdKeyResolver; + private readonly IAuditRepository _auditRepository; + + protected FileServiceOperationBase(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, TRepository repository, ILogger logger, IUserIdKeyResolver userIdKeyResolver, IAuditRepository auditRepository) + : base(provider, loggerFactory, eventMessagesFactory, repository) + { + _logger = logger; + _userIdKeyResolver = userIdKeyResolver; + _auditRepository = auditRepository; + } + + protected abstract TOperationStatus Success { get; } + + protected abstract TOperationStatus NotFound { get; } + + protected abstract TOperationStatus CancelledByNotification { get; } + + protected abstract TOperationStatus PathTooLong { get; } + + protected abstract TOperationStatus AlreadyExists { get; } + + protected abstract TOperationStatus ParentNotFound { get; } + + protected abstract TOperationStatus InvalidName { get; } + + protected abstract TOperationStatus InvalidFileExtension { get; } + + protected abstract string EntityType { get; } + + protected abstract SavingNotification SavingNotification(TEntity target, EventMessages messages); + + protected abstract SavedNotification SavedNotification(TEntity target, EventMessages messages); + + protected abstract DeletingNotification DeletingNotification(TEntity target, EventMessages messages); + + protected abstract DeletedNotification DeletedNotification(TEntity target, EventMessages messages); + + protected abstract TEntity CreateEntity(string path, string? content); + + protected async Task HandleDeleteAsync(string path, Guid userKey) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + + TEntity? entity = Repository.Get(path); + if (entity is null) + { + return NotFound; + } + + EventMessages eventMessages = EventMessagesFactory.Get(); + DeletingNotification deletingNotification = DeletingNotification(entity, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(deletingNotification)) + { + return CancelledByNotification; + } + + Repository.Delete(entity); + + scope.Notifications.Publish(DeletedNotification(entity, eventMessages).WithStateFrom(deletingNotification)); + await AuditAsync(AuditType.Delete, userKey); + + scope.Complete(); + return Success; + } + + protected async Task> HandleCreateAsync(string name, string? parentPath, string? content, Guid userKey) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + + var path = GetFilePath(parentPath, name); + try + { + TOperationStatus validationResult = ValidateCreate(path); + if (Success.Equals(validationResult) is false) + { + return Attempt.FailWithStatus(validationResult, default); + } + } + catch (PathTooLongException exception) + { + _logger.LogError(exception, "The {EntityType} path was too long", EntityType); + return Attempt.FailWithStatus(PathTooLong, default); + } + + TEntity entity = CreateEntity(path, content); + + EventMessages eventMessages = EventMessagesFactory.Get(); + SavingNotification savingNotification = SavingNotification(entity, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(savingNotification)) + { + return Attempt.FailWithStatus(CancelledByNotification, default); + } + + Repository.Save(entity); + + scope.Notifications.Publish(SavedNotification(entity, eventMessages).WithStateFrom(savingNotification)); + await AuditAsync(AuditType.Save, userKey); + + scope.Complete(); + return Attempt.SucceedWithStatus(Success, entity); + } + + protected async Task> HandleUpdateAsync(string path, string content, Guid userKey) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + + TEntity? entity = Repository.Get(path); + + if (entity is null) + { + return Attempt.FailWithStatus(NotFound, default); + } + + entity.Content = content; + + EventMessages eventMessages = EventMessagesFactory.Get(); + SavingNotification savingNotification = SavingNotification(entity, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(savingNotification)) + { + return Attempt.FailWithStatus(CancelledByNotification, default); + } + + Repository.Save(entity); + + scope.Notifications.Publish(SavedNotification(entity, eventMessages).WithStateFrom(savingNotification)); + await AuditAsync(AuditType.Save, userKey); + + scope.Complete(); + return Attempt.SucceedWithStatus(Success, entity); + } + + protected async Task> HandleRenameAsync(string path, string newName, Guid userKey) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(); + + TEntity? entity = Repository.Get(path); + + if (entity is null) + { + return Attempt.FailWithStatus(NotFound, default); + } + + var newPath = ReplaceFileName(path, newName); + + try + { + TOperationStatus validationResult = ValidateRename(newName, newPath); + if (Success.Equals(validationResult) is false) + { + return Attempt.FailWithStatus(validationResult, default); + } + } + catch (PathTooLongException exception) + { + _logger.LogError(exception, "The {EntityType} path was too long", EntityType); + return Attempt.FailWithStatus(PathTooLong, default); + } + + entity.Path = newPath; + + EventMessages eventMessages = EventMessagesFactory.Get(); + SavingNotification savingNotification = SavingNotification(entity, eventMessages); + if (await scope.Notifications.PublishCancelableAsync(savingNotification)) + { + return Attempt.FailWithStatus(CancelledByNotification, default); + } + + Repository.Save(entity); + + scope.Notifications.Publish(SavedNotification(entity, eventMessages).WithStateFrom(savingNotification)); + await AuditAsync(AuditType.Save, userKey); + + scope.Complete(); + return Attempt.SucceedWithStatus(Success, entity); + } + + private TOperationStatus ValidateCreate(string path) + { + if (Repository.Exists(path)) + { + return AlreadyExists; + } + + var directoryPath = GetDirectoryPath(path); + if (directoryPath.IsNullOrWhiteSpace() is false && Repository.FolderExists(directoryPath) is false) + { + return ParentNotFound; + } + + var fileName = GetFileName(path); + if (HasValidFileName(fileName) is false) + { + return InvalidName; + } + + if (HasValidFileExtension(fileName) is false) + { + return InvalidFileExtension; + } + + return Success; + } + + private TOperationStatus ValidateRename(string newName, string newPath) + { + if (Repository.Exists(newPath)) + { + return AlreadyExists; + } + + if (HasValidFileName(newName) is false) + { + return InvalidName; + } + + if (HasValidFileExtension(newName) is false) + { + return InvalidFileExtension; + } + + return Success; + } + + private async Task AuditAsync(AuditType type, Guid userKey) + { + var userId = await _userIdKeyResolver.GetAsync(userKey); + _auditRepository.Save(new AuditItem(-1, type, userId, EntityType)); + } + + private string GetFilePath(string? parentPath, string fileName) + => Path.Join(parentPath, fileName); + + private string ReplaceFileName(string path, string newName) + => Path.Join(GetDirectoryPath(path), newName); + + private string GetDirectoryPath(string path) + => Path.GetDirectoryName(path) ?? string.Empty; + + private string GetFileName(string path) + => Path.GetFileName(path); +} diff --git a/src/Umbraco.Core/Services/FileSystem/FolderServiceOperationBase.cs b/src/Umbraco.Core/Services/FileSystem/FolderServiceOperationBase.cs new file mode 100644 index 0000000000..68523d1443 --- /dev/null +++ b/src/Umbraco.Core/Services/FileSystem/FolderServiceOperationBase.cs @@ -0,0 +1,140 @@ +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Services.FileSystem; + +internal abstract class FolderServiceOperationBase + where TRepository : IFileWithFoldersRepository + where TFolderModel : FolderModelBase, new() + where TOperationStatus : Enum +{ + private readonly TRepository _repository; + + private readonly ICoreScopeProvider _scopeProvider; + + protected FolderServiceOperationBase(TRepository repository, ICoreScopeProvider scopeProvider) + { + _repository = repository; + _scopeProvider = scopeProvider; + } + + protected abstract TOperationStatus Success { get; } + + protected abstract TOperationStatus NotFound { get; } + + protected abstract TOperationStatus NotEmpty { get; } + + protected abstract TOperationStatus AlreadyExists { get; } + + protected abstract TOperationStatus ParentNotFound { get; } + + protected abstract TOperationStatus InvalidName { get; } + + protected async Task> HandleCreateAsync(string name, string? parentPath) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + + var path = GetFolderPath(name, parentPath); + TOperationStatus validateResult = ValidateCreate(name, path, parentPath); + if (Success.Equals(validateResult) is false) + { + return Attempt.FailWithStatus(validateResult, null); + } + + _repository.AddFolder(path); + + scope.Complete(); + + var result = new TFolderModel + { + Name = name, + Path = path, + ParentPath = GetParentFolderPath(path) + }; + return await Task.FromResult(Attempt.SucceedWithStatus(Success, result)); + } + + protected async Task HandleDeleteAsync(string path) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + + TOperationStatus validateResult = ValidateDelete(path); + if (Success.Equals(validateResult) is false) + { + return await Task.FromResult(validateResult); + } + + _repository.DeleteFolder(path); + + scope.Complete(); + + return Success; + } + + protected async Task HandleGetAsync(string path) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + + // There's not much we can actually get when it's a folder, so it more a matter of ensuring the folder exists and returning a model. + if (_repository.FolderExists(path) is false) + { + return await Task.FromResult(null); + } + + var result = new TFolderModel + { + Name = GetFolderName(path), + Path = path, + ParentPath = GetParentFolderPath(path) + }; + + scope.Complete(); + return result; + } + + private TOperationStatus ValidateCreate(string name, string path, string? parentPath) + { + if (name.ContainsAny(Path.GetInvalidFileNameChars())) + { + return InvalidName; + } + + if (_repository.FolderExists(path)) + { + return AlreadyExists; + } + + if (string.IsNullOrWhiteSpace(parentPath) is false && _repository.FolderExists(parentPath) is false) + { + return ParentNotFound; + } + + return Success; + } + + private TOperationStatus ValidateDelete(string path) + { + if (_repository.FolderExists(path) is false) + { + return NotFound; + } + + if (_repository.FolderHasContent(path)) + { + return NotEmpty; + } + + return Success; + } + + private string GetFolderPath(string folderName, string? parentPath) + => Path.Join(parentPath, folderName); + + private string GetFolderName(string path) + => Path.GetFileName(path); + + private string? GetParentFolderPath(string path) + => Path.GetDirectoryName(path); +} diff --git a/src/Umbraco.Core/Services/FileSystem/IPartialViewFolderService.cs b/src/Umbraco.Core/Services/FileSystem/IPartialViewFolderService.cs new file mode 100644 index 0000000000..e35144623a --- /dev/null +++ b/src/Umbraco.Core/Services/FileSystem/IPartialViewFolderService.cs @@ -0,0 +1,13 @@ +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services.FileSystem; + +public interface IPartialViewFolderService +{ + Task GetAsync(string path); + + Task> CreateAsync(PartialViewFolderCreateModel createModel); + + Task DeleteAsync(string path); +} diff --git a/src/Umbraco.Core/Services/FileSystem/IScriptFolderService.cs b/src/Umbraco.Core/Services/FileSystem/IScriptFolderService.cs new file mode 100644 index 0000000000..7fe2efe7c9 --- /dev/null +++ b/src/Umbraco.Core/Services/FileSystem/IScriptFolderService.cs @@ -0,0 +1,13 @@ +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services.FileSystem; + +public interface IScriptFolderService +{ + Task GetAsync(string path); + + Task> CreateAsync(ScriptFolderCreateModel createModel); + + Task DeleteAsync(string path); +} diff --git a/src/Umbraco.Core/Services/FileSystem/IStylesheetFolderService.cs b/src/Umbraco.Core/Services/FileSystem/IStylesheetFolderService.cs new file mode 100644 index 0000000000..f17163405f --- /dev/null +++ b/src/Umbraco.Core/Services/FileSystem/IStylesheetFolderService.cs @@ -0,0 +1,13 @@ +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services.FileSystem; + +public interface IStylesheetFolderService +{ + Task GetAsync(string path); + + Task> CreateAsync(StylesheetFolderCreateModel createModel); + + Task DeleteAsync(string path); +} diff --git a/src/Umbraco.Core/Services/FileSystem/PartialViewFolderService.cs b/src/Umbraco.Core/Services/FileSystem/PartialViewFolderService.cs new file mode 100644 index 0000000000..43b631d45b --- /dev/null +++ b/src/Umbraco.Core/Services/FileSystem/PartialViewFolderService.cs @@ -0,0 +1,35 @@ +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services.FileSystem; + +internal class PartialViewFolderService : FolderServiceOperationBase, IPartialViewFolderService +{ + public PartialViewFolderService(IPartialViewRepository repository, ICoreScopeProvider scopeProvider) + : base(repository, scopeProvider) + { + } + + protected override PartialViewFolderOperationStatus Success => PartialViewFolderOperationStatus.Success; + + protected override PartialViewFolderOperationStatus NotFound => PartialViewFolderOperationStatus.NotFound; + + protected override PartialViewFolderOperationStatus NotEmpty => PartialViewFolderOperationStatus.NotEmpty; + + protected override PartialViewFolderOperationStatus AlreadyExists => PartialViewFolderOperationStatus.AlreadyExists; + + protected override PartialViewFolderOperationStatus ParentNotFound => PartialViewFolderOperationStatus.ParentNotFound; + + protected override PartialViewFolderOperationStatus InvalidName => PartialViewFolderOperationStatus.InvalidName; + + + public async Task GetAsync(string path) + => await HandleGetAsync(path); + + public async Task> CreateAsync(PartialViewFolderCreateModel createModel) + => await HandleCreateAsync(createModel.Name, createModel.ParentPath); + + public async Task DeleteAsync(string path) => await HandleDeleteAsync(path); +} diff --git a/src/Umbraco.Core/Services/FileSystem/ScriptFolderService.cs b/src/Umbraco.Core/Services/FileSystem/ScriptFolderService.cs new file mode 100644 index 0000000000..dca28af69c --- /dev/null +++ b/src/Umbraco.Core/Services/FileSystem/ScriptFolderService.cs @@ -0,0 +1,35 @@ +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services.FileSystem; + +internal class ScriptFolderService : FolderServiceOperationBase, IScriptFolderService +{ + public ScriptFolderService(IScriptRepository repository, ICoreScopeProvider scopeProvider) + : base(repository, scopeProvider) + { + } + + protected override ScriptFolderOperationStatus Success => ScriptFolderOperationStatus.Success; + + protected override ScriptFolderOperationStatus NotFound => ScriptFolderOperationStatus.NotFound; + + protected override ScriptFolderOperationStatus NotEmpty => ScriptFolderOperationStatus.NotEmpty; + + protected override ScriptFolderOperationStatus AlreadyExists => ScriptFolderOperationStatus.AlreadyExists; + + protected override ScriptFolderOperationStatus ParentNotFound => ScriptFolderOperationStatus.ParentNotFound; + + protected override ScriptFolderOperationStatus InvalidName => ScriptFolderOperationStatus.InvalidName; + + + public async Task GetAsync(string path) + => await HandleGetAsync(path); + + public async Task> CreateAsync(ScriptFolderCreateModel createModel) + => await HandleCreateAsync(createModel.Name, createModel.ParentPath); + + public async Task DeleteAsync(string path) => await HandleDeleteAsync(path); +} diff --git a/src/Umbraco.Core/Services/FileSystem/StylesheetFolderService.cs b/src/Umbraco.Core/Services/FileSystem/StylesheetFolderService.cs new file mode 100644 index 0000000000..b153ca0845 --- /dev/null +++ b/src/Umbraco.Core/Services/FileSystem/StylesheetFolderService.cs @@ -0,0 +1,35 @@ +using Umbraco.Cms.Core.Models.FileSystem; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Core.Services.FileSystem; + +internal class StylesheetFolderService : FolderServiceOperationBase, IStylesheetFolderService +{ + public StylesheetFolderService(IStylesheetRepository repository, ICoreScopeProvider scopeProvider) + : base(repository, scopeProvider) + { + } + + protected override StylesheetFolderOperationStatus Success => StylesheetFolderOperationStatus.Success; + + protected override StylesheetFolderOperationStatus NotFound => StylesheetFolderOperationStatus.NotFound; + + protected override StylesheetFolderOperationStatus NotEmpty => StylesheetFolderOperationStatus.NotEmpty; + + protected override StylesheetFolderOperationStatus AlreadyExists => StylesheetFolderOperationStatus.AlreadyExists; + + protected override StylesheetFolderOperationStatus ParentNotFound => StylesheetFolderOperationStatus.ParentNotFound; + + protected override StylesheetFolderOperationStatus InvalidName => StylesheetFolderOperationStatus.InvalidName; + + + public async Task GetAsync(string path) + => await HandleGetAsync(path); + + public async Task> CreateAsync(StylesheetFolderCreateModel createModel) + => await HandleCreateAsync(createModel.Name, createModel.ParentPath); + + public async Task DeleteAsync(string path) => await HandleDeleteAsync(path); +} diff --git a/src/Umbraco.Core/Services/IPartialViewFolderService.cs b/src/Umbraco.Core/Services/IPartialViewFolderService.cs deleted file mode 100644 index 6607068a54..0000000000 --- a/src/Umbraco.Core/Services/IPartialViewFolderService.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Umbraco.Cms.Core.Services.OperationStatus; - -namespace Umbraco.Cms.Core.Services; - -public interface IPartialViewFolderService : IPathFolderService -{ - -} diff --git a/src/Umbraco.Core/Services/IPartialViewService.cs b/src/Umbraco.Core/Services/IPartialViewService.cs index 567a4686af..c547183e31 100644 --- a/src/Umbraco.Core/Services/IPartialViewService.cs +++ b/src/Umbraco.Core/Services/IPartialViewService.cs @@ -1,12 +1,12 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Core.Snippets; using PartialViewSnippet = Umbraco.Cms.Core.Snippets.PartialViewSnippet; namespace Umbraco.Cms.Core.Services; public interface IPartialViewService : IBasicFileService { - /// /// Deletes a partial view. /// @@ -16,19 +16,19 @@ public interface IPartialViewService : IBasicFileService Task DeleteAsync(string path, Guid userKey); /// - /// Gets the name of all the available partial view snippets. + /// Gets all the available partial view snippets. /// /// Amount to skip. /// Amount to take. /// - Task> GetSnippetNamesAsync(int skip, int take); + Task> GetSnippetsAsync(int skip, int take); /// - /// Gets a partial view snippet by name, returns null if not found. + /// Gets a partial view snippet by ID, returns null if not found. /// - /// The name of the snippet to get. + /// The name of the snippet to get. /// The partial view snippet, null if not found. - Task GetSnippetByNameAsync(string name); + Task GetSnippetAsync(string id); /// /// Creates a new partial view. @@ -41,8 +41,18 @@ public interface IPartialViewService : IBasicFileService /// /// Updates an existing partial view. /// + /// The path of the partial view to update. /// A with the changes. /// The key of the user performing the operation. /// An attempt indicating if the operation was a success as well as a more detailed . - Task> UpdateAsync(PartialViewUpdateModel updateModel, Guid userKey); + Task> UpdateAsync(string path, PartialViewUpdateModel updateModel, Guid userKey); + + /// + /// Renames a partial view. + /// + /// The path of the partial view to rename. + /// A with the changes. + /// The key of the user performing the operation. + /// An attempt indicating if the operation was a success as well as a more detailed . + Task> RenameAsync(string path, PartialViewRenameModel renameModel, Guid userKey); } diff --git a/src/Umbraco.Core/Services/IPathFolderService.cs b/src/Umbraco.Core/Services/IPathFolderService.cs deleted file mode 100644 index e40482a354..0000000000 --- a/src/Umbraco.Core/Services/IPathFolderService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Umbraco.Cms.Core.Models; - -namespace Umbraco.Cms.Core.Services; - -public interface IPathFolderService where TStatus : Enum -{ - Task GetAsync(string path); - - Task> CreateAsync(PathContainer container); - - Task> DeleteAsync(string path); -} diff --git a/src/Umbraco.Core/Services/IScriptFolderService.cs b/src/Umbraco.Core/Services/IScriptFolderService.cs deleted file mode 100644 index d81cb9a057..0000000000 --- a/src/Umbraco.Core/Services/IScriptFolderService.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Umbraco.Cms.Core.Services.OperationStatus; - -namespace Umbraco.Cms.Core.Services; - -public interface IScriptFolderService : IPathFolderService -{ - -} diff --git a/src/Umbraco.Core/Services/IScriptService.cs b/src/Umbraco.Core/Services/IScriptService.cs index 24f5c8a86f..157e18f0aa 100644 --- a/src/Umbraco.Core/Services/IScriptService.cs +++ b/src/Umbraco.Core/Services/IScriptService.cs @@ -16,10 +16,11 @@ public interface IScriptService : IBasicFileService /// /// Updates an existing script. /// + /// The path of the script to update. /// A with the changes. /// The key of the user performing the operation. /// An attempt indicating if the operation was a success as well as a more detailed . - Task> UpdateAsync(ScriptUpdateModel updateModel, Guid userKey); + Task> UpdateAsync(string path, ScriptUpdateModel updateModel, Guid userKey); /// /// Deletes a Script. @@ -28,4 +29,13 @@ public interface IScriptService : IBasicFileService /// The key of the user performing the operation. /// An operation status. Task DeleteAsync(string path, Guid userKey); + + /// + /// Renames a script. + /// + /// The path of the script to rename. + /// A with the changes. + /// The key of the user performing the operation. + /// An attempt indicating if the operation was a success as well as a more detailed . + Task> RenameAsync(string path, ScriptRenameModel renameModel, Guid userKey); } diff --git a/src/Umbraco.Core/Services/IStylesheetFolderService.cs b/src/Umbraco.Core/Services/IStylesheetFolderService.cs deleted file mode 100644 index 52b7c57c23..0000000000 --- a/src/Umbraco.Core/Services/IStylesheetFolderService.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Umbraco.Cms.Core.Services.OperationStatus; - -namespace Umbraco.Cms.Core.Services; - -public interface IStylesheetFolderService : IPathFolderService -{ - -} diff --git a/src/Umbraco.Core/Services/IStylesheetService.cs b/src/Umbraco.Core/Services/IStylesheetService.cs index 131c4d1ac3..807fb4285a 100644 --- a/src/Umbraco.Core/Services/IStylesheetService.cs +++ b/src/Umbraco.Core/Services/IStylesheetService.cs @@ -16,10 +16,11 @@ public interface IStylesheetService : IBasicFileService /// /// Updates an existing stylesheet. /// + /// The path of the stylesheet to update. /// A with the changes. /// The key of the user performing the operation. /// An attempt indicating if the operation was a success as well as a more detailed . - Task> UpdateAsync(StylesheetUpdateModel updateModel, Guid userKey); + Task> UpdateAsync(string path, StylesheetUpdateModel updateModel, Guid userKey); /// /// Deletes a stylesheet. @@ -28,4 +29,13 @@ public interface IStylesheetService : IBasicFileService /// The key of the user performing the operation. /// An operation status. Task DeleteAsync(string path, Guid userKey); + + /// + /// Renames a stylesheet. + /// + /// The path of the stylesheet to rename. + /// A with the changes. + /// The key of the user performing the operation. + /// An attempt indicating if the operation was a success as well as a more detailed . + Task> RenameAsync(string path, StylesheetRenameModel renameModel, Guid userKey); } diff --git a/src/Umbraco.Core/Services/PartialViewFolderService.cs b/src/Umbraco.Core/Services/PartialViewFolderService.cs deleted file mode 100644 index 0d6201a859..0000000000 --- a/src/Umbraco.Core/Services/PartialViewFolderService.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services.OperationStatus; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Services; - -public class PartialViewFolderService : PathFolderServiceBase, IPartialViewFolderService -{ - private readonly IPartialViewRepository _partialViewRepository; - - public PartialViewFolderService( - ICoreScopeProvider provider, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IPartialViewRepository partialViewRepository) - : base(provider, loggerFactory, eventMessagesFactory) - { - _partialViewRepository = partialViewRepository; - } - - protected override IPartialViewRepository Repository => _partialViewRepository; - - protected override PartialViewFolderOperationStatus SuccessStatus => PartialViewFolderOperationStatus.Success; - - protected override Task> ValidateCreateAsync(PathContainer container) - { - if(container.Name.ContainsAny(Path.GetInvalidFileNameChars())) - { - return Task.FromResult(Attempt.Fail(PartialViewFolderOperationStatus.InvalidName)); - } - - if(_partialViewRepository.FolderExists(container.Path)) - { - return Task.FromResult(Attempt.Fail(PartialViewFolderOperationStatus.AlreadyExists)); - } - - if(string.IsNullOrWhiteSpace(container.ParentPath) is false && - _partialViewRepository.FolderExists(container.ParentPath) is false) - { - return Task.FromResult(Attempt.Fail(PartialViewFolderOperationStatus.ParentNotFound)); - } - - return Task.FromResult(Attempt.Succeed(PartialViewFolderOperationStatus.Success)); - } - - protected override Task> ValidateDeleteAsync(string path) - { - if (_partialViewRepository.FolderExists(path) is false) - { - return Task.FromResult(Attempt.Fail(PartialViewFolderOperationStatus.NotFound)); - } - - if (_partialViewRepository.FolderHasContent(path)) - { - return Task.FromResult(Attempt.Fail(PartialViewFolderOperationStatus.NotEmpty)); - } - - return Task.FromResult(Attempt.Succeed(PartialViewFolderOperationStatus.Success)); - } -} diff --git a/src/Umbraco.Core/Services/PartialViewService.cs b/src/Umbraco.Core/Services/PartialViewService.cs index 99b111021c..ba636755a4 100644 --- a/src/Umbraco.Core/Services/PartialViewService.cs +++ b/src/Umbraco.Core/Services/PartialViewService.cs @@ -10,217 +10,92 @@ using PartialViewSnippet = Umbraco.Cms.Core.Snippets.PartialViewSnippet; namespace Umbraco.Cms.Core.Services; -public class PartialViewService : FileServiceBase, IPartialViewService +public class PartialViewService : FileServiceOperationBase, IPartialViewService { private readonly PartialViewSnippetCollection _snippetCollection; - private readonly IUserIdKeyResolver _userIdKeyResolver; - private readonly ILogger _logger; - private readonly IAuditRepository _auditRepository; - - protected override string[] AllowedFileExtensions { get; } = { ".cshtml" }; public PartialViewService( ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - PartialViewSnippetCollection snippetCollection, - IPartialViewRepository partialViewRepository, + IPartialViewRepository repository, + ILogger logger, IUserIdKeyResolver userIdKeyResolver, - ILogger logger, - IAuditRepository auditRepository) - : base(provider, loggerFactory, eventMessagesFactory, partialViewRepository) + IAuditRepository auditRepository, + PartialViewSnippetCollection snippetCollection) + : base(provider, loggerFactory, eventMessagesFactory, repository, logger, userIdKeyResolver, auditRepository) + => _snippetCollection = snippetCollection; + + protected override string[] AllowedFileExtensions { get; } = { ".cshtml" }; + + protected override PartialViewOperationStatus Success => PartialViewOperationStatus.Success; + + protected override PartialViewOperationStatus NotFound => PartialViewOperationStatus.NotFound; + + protected override PartialViewOperationStatus CancelledByNotification => PartialViewOperationStatus.CancelledByNotification; + + protected override PartialViewOperationStatus PathTooLong => PartialViewOperationStatus.PathTooLong; + + protected override PartialViewOperationStatus AlreadyExists => PartialViewOperationStatus.AlreadyExists; + + protected override PartialViewOperationStatus ParentNotFound => PartialViewOperationStatus.ParentNotFound; + + protected override PartialViewOperationStatus InvalidName => PartialViewOperationStatus.InvalidName; + + protected override PartialViewOperationStatus InvalidFileExtension => PartialViewOperationStatus.InvalidFileExtension; + + protected override string EntityType => "PartialView"; + + protected override PartialViewSavingNotification SavingNotification(IPartialView target, EventMessages messages) + => new(target, messages); + + protected override PartialViewSavedNotification SavedNotification(IPartialView target, EventMessages messages) + => new(target, messages); + + protected override PartialViewDeletingNotification DeletingNotification(IPartialView target, EventMessages messages) + => new(target, messages); + + protected override PartialViewDeletedNotification DeletedNotification(IPartialView target, EventMessages messages) + => new(target, messages); + + protected override IPartialView CreateEntity(string path, string? content) + => new PartialView(PartialViewType.PartialView, path) { Content = content }; + + /// + public async Task> GetSnippetsAsync(int skip, int take) { - _snippetCollection = snippetCollection; - _userIdKeyResolver = userIdKeyResolver; - _logger = logger; - _auditRepository = auditRepository; + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + var result = new PagedModel( + _snippetCollection.Count, + _snippetCollection + .Skip(skip) + .Take(take) + .Select(snippet => new PartialViewSnippetSlim(snippet.Id, snippet.Name)) + .ToArray()); + return await Task.FromResult(result); + } + + /// + public async Task GetSnippetAsync(string id) + { + using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); + PartialViewSnippet? snippet = _snippetCollection.FirstOrDefault(s => s.Id == id); + return await Task.FromResult(snippet); } /// public async Task DeleteAsync(string path, Guid userKey) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); - - IPartialView? partialView = Repository.Get(path); - if (partialView is null) - { - return PartialViewOperationStatus.NotFound; - } - - EventMessages eventMessages = EventMessagesFactory.Get(); - - var deletingNotification = new PartialViewDeletingNotification(partialView, eventMessages); - if (await scope.Notifications.PublishCancelableAsync(deletingNotification)) - { - return PartialViewOperationStatus.CancelledByNotification; - } - - Repository.Delete(partialView); - - scope.Notifications.Publish( - new PartialViewDeletedNotification(partialView, eventMessages).WithStateFrom(deletingNotification)); - - await AuditAsync(AuditType.Delete, userKey); - return PartialViewOperationStatus.Success; - } - - /// - public Task> GetSnippetNamesAsync(int skip, int take) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); - - string[] names = _snippetCollection.GetNames().ToArray(); - var total = names.Length; - - IEnumerable snippets = names - .Skip(skip) - .Take(take); - - return Task.FromResult(new PagedModel(total, snippets)); - } - - /// - public Task GetSnippetByNameAsync(string name) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true); - - // A bit weird but the "Name" of the snippet is the file name and extensions - // However when getting the content it's just the name without the extension - var fileName = name + ".cshtml"; - if (_snippetCollection.Any(x => x.Name == fileName) is false) - { - return Task.FromResult(null); - } - - var content = _snippetCollection.GetContentFromName(name); - var snippet = new PartialViewSnippet(name, content); - - return Task.FromResult(snippet); - } + => await HandleDeleteAsync(path, userKey); /// public async Task> CreateAsync(PartialViewCreateModel createModel, Guid userKey) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); - - try - { - PartialViewOperationStatus validationResult = ValidateCreate(createModel); - if (validationResult is not PartialViewOperationStatus.Success) - { - return Attempt.FailWithStatus(validationResult, null); - } - } - catch (PathTooLongException exception) - { - _logger.LogError(exception, "The partial view path was too long"); - return Attempt.FailWithStatus(PartialViewOperationStatus.PathTooLong, null); - } - - var partialView = new PartialView(PartialViewType.PartialView, createModel.FilePath) { Content = createModel.Content }; - - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new PartialViewSavingNotification(partialView, eventMessages); - if (await scope.Notifications.PublishCancelableAsync(savingNotification)) - { - return Attempt.FailWithStatus( - PartialViewOperationStatus.CancelledByNotification, null); - } - - Repository.Save(partialView); - scope.Notifications.Publish(new PartialViewSavedNotification(partialView, eventMessages).WithStateFrom(savingNotification)); - await AuditAsync(AuditType.Save, userKey); - - scope.Complete(); - return Attempt.SucceedWithStatus(PartialViewOperationStatus.Success, partialView); - } - - private PartialViewOperationStatus ValidateCreate(PartialViewCreateModel createModel) - { - if (Repository.Exists(createModel.FilePath)) - { - return PartialViewOperationStatus.AlreadyExists; - } - - if (string.IsNullOrWhiteSpace(createModel.ParentPath) is false && - Repository.FolderExists(createModel.ParentPath) is false) - { - return PartialViewOperationStatus.ParentNotFound; - } - - if (HasValidFileName(createModel.Name) is false) - { - return PartialViewOperationStatus.InvalidName; - } - - if (HasValidFileExtension(createModel.FilePath) is false) - { - return PartialViewOperationStatus.InvalidFileExtension; - } - - return PartialViewOperationStatus.Success; - } + => await HandleCreateAsync(createModel.Name, createModel.ParentPath, createModel.Content, userKey); /// - public async Task> UpdateAsync(PartialViewUpdateModel updateModel, Guid userKey) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); - IPartialView? partialView = Repository.Get(updateModel.ExistingPath); + public async Task> UpdateAsync(string path, PartialViewUpdateModel updateModel, Guid userKey) + => await HandleUpdateAsync(path, updateModel.Content, userKey); - if (partialView is null) - { - return Attempt.FailWithStatus(PartialViewOperationStatus.NotFound, null); - } - - PartialViewOperationStatus validationResult = ValidateUpdate(updateModel); - if (validationResult is not PartialViewOperationStatus.Success) - { - return Attempt.FailWithStatus(validationResult, null); - } - - partialView.Content = updateModel.Content; - if (partialView.Name != updateModel.Name) - { - var newPath = partialView.Path.Replace(partialView.Name!, updateModel.Name); - partialView.Path = newPath; - } - - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new PartialViewSavingNotification(partialView, eventMessages); - if (await scope.Notifications.PublishCancelableAsync(savingNotification)) - { - return Attempt.FailWithStatus(PartialViewOperationStatus.CancelledByNotification, null); - } - - Repository.Save(partialView); - scope.Notifications.Publish(new PartialViewSavedNotification(partialView, eventMessages).WithStateFrom(savingNotification)); - - await AuditAsync(AuditType.Save, userKey); - - scope.Complete(); - return Attempt.SucceedWithStatus(PartialViewOperationStatus.Success, partialView); - } - - private PartialViewOperationStatus ValidateUpdate(PartialViewUpdateModel updateModel) - { - if (HasValidFileExtension(updateModel.Name) is false) - { - return PartialViewOperationStatus.InvalidFileExtension; - } - - if (HasValidFileName(updateModel.Name) is false) - { - return PartialViewOperationStatus.InvalidName; - } - - return PartialViewOperationStatus.Success; - } - - private async Task AuditAsync(AuditType type, Guid userKey) - { - int userId = await _userIdKeyResolver.GetAsync(userKey); - - // We're passing -1 here, because we don't have an entity id to pass in, as files on disc are not entities - _auditRepository.Save(new AuditItem(-1, type, userId, "PartialView")); - } + /// + public async Task> RenameAsync(string path, PartialViewRenameModel renameModel, Guid userKey) + => await HandleRenameAsync(path, renameModel.Name, userKey); } diff --git a/src/Umbraco.Core/Services/PathFolderServiceBase.cs b/src/Umbraco.Core/Services/PathFolderServiceBase.cs deleted file mode 100644 index 62f07c2e51..0000000000 --- a/src/Umbraco.Core/Services/PathFolderServiceBase.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; - -namespace Umbraco.Cms.Core.Services; - -public abstract class PathFolderServiceBase : - RepositoryService, - IPathFolderService - where TRepo: IFileWithFoldersRepository - where TStatus : Enum -{ - protected PathFolderServiceBase(ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory) : base(provider, loggerFactory, eventMessagesFactory) - { - } - - protected abstract TRepo Repository { get; } - - protected abstract TStatus SuccessStatus { get; } - - public virtual Task GetAsync(string path) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); - - // There's not much we can actually get when it's a folder, so it more a matter of ensuring the folder exists and returning a model. - if (Repository.FolderExists(path) is false) - { - return Task.FromResult(null); - } - - PathContainer model = CreateFromPath(path); - - scope.Complete(); - return Task.FromResult(model); - } - - public async Task> CreateAsync(PathContainer container) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); - - Attempt validationResult = await ValidateCreateAsync(container); - if (validationResult.Success is false) - { - return Attempt.FailWithStatus(validationResult.Result!, null); - } - - Repository.AddFolder(container.Path); - - scope.Complete(); - - return Attempt.SucceedWithStatus(SuccessStatus, CreateFromPath(container.Path)); - } - - protected abstract Task> ValidateCreateAsync(PathContainer container); - - public async Task> DeleteAsync(string path) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); - - Attempt validationResult = await ValidateDeleteAsync(path); - if (validationResult.Success is false) - { - return Attempt.Fail(validationResult.Result); - } - - Repository.DeleteFolder(path); - - scope.Complete(); - - return Attempt.Succeed(SuccessStatus); - } - - protected abstract Task> ValidateDeleteAsync(string path); - - private PathContainer CreateFromPath(string path) - { - var parentPath = Path.GetDirectoryName(path); - var parentPathLength = string.IsNullOrEmpty(parentPath) ? 0 : parentPath.Length + 1; - - var model = new PathContainer - { - Name = path.Remove(0, parentPathLength), - ParentPath = parentPath, - }; - - return model; - } -} diff --git a/src/Umbraco.Core/Services/RichTextStylesheetService.cs b/src/Umbraco.Core/Services/RichTextStylesheetService.cs deleted file mode 100644 index 46fde91d0a..0000000000 --- a/src/Umbraco.Core/Services/RichTextStylesheetService.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Services.OperationStatus; -using Umbraco.Cms.Core.Strings.Css; - -namespace Umbraco.Cms.Core.Services; - -public class RichTextStylesheetService : IRichTextStylesheetService -{ - private readonly IStylesheetService _stylesheetService; - - public RichTextStylesheetService(IStylesheetService stylesheetService) - { - _stylesheetService = stylesheetService; - } - - public Task InterpolateRichTextRules(RichTextStylesheetData data) - { - // First we remove all existing rules, from the content, in case some of the rules has changed or been removed - // This way we can simply re-add them. - StylesheetRule[] existingRules = string.IsNullOrWhiteSpace(data.Content) - ? Array.Empty() - : StylesheetHelper.ParseRules(data.Content).ToArray(); - - foreach (StylesheetRule rule in existingRules) - { - // Setting the rule to null will delete it from the content. - data.Content = StylesheetHelper.ReplaceRule(data.Content, rule.Name, null); - } - - data.Content = data.Content?.TrimEnd(Constants.CharArrays.LineFeedCarriageReturn); - - // Now we can re-add all the rules. - if (data.Rules.Any()) - { - foreach (StylesheetRule rule in data.Rules) - { - data.Content = StylesheetHelper.AppendRule( - data.Content, - rule); - } - - data.Content += Environment.NewLine; - } - - return Task.FromResult(data.Content ?? string.Empty); - } - - public Task> ExtractRichTextRules(RichTextStylesheetData data) - { - if (string.IsNullOrWhiteSpace(data.Content)) - { - return Task.FromResult(Enumerable.Empty()); - } - - return Task.FromResult(StylesheetHelper.ParseRules(data.Content)); - } - - public async Task, StylesheetOperationStatus>> GetRulesByPathAsync(string path) - { - IStylesheet? stylesheet = await _stylesheetService.GetAsync(path); - - if (stylesheet is null) - { - return Attempt.FailWithStatus(StylesheetOperationStatus.NotFound, Enumerable.Empty()); - } - - IEnumerable rules = stylesheet.Properties is null - ? Enumerable.Empty() - : stylesheet.Properties.Select(x => new StylesheetRule { Name = x.Name, Selector = x.Alias, Styles = x.Value }); - - return Attempt.SucceedWithStatus(StylesheetOperationStatus.Success, rules); - } -} diff --git a/src/Umbraco.Core/Services/ScriptFolderService.cs b/src/Umbraco.Core/Services/ScriptFolderService.cs deleted file mode 100644 index 272253a0e3..0000000000 --- a/src/Umbraco.Core/Services/ScriptFolderService.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services.OperationStatus; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Services; - -public class ScriptFolderService : PathFolderServiceBase, IScriptFolderService -{ - private readonly IScriptRepository _scriptRepository; - - public ScriptFolderService( - ICoreScopeProvider provider, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IScriptRepository scriptRepository) - : base(provider, loggerFactory, eventMessagesFactory) - { - _scriptRepository = scriptRepository; - } - - protected override IScriptRepository Repository => _scriptRepository; - - protected override ScriptFolderOperationStatus SuccessStatus => ScriptFolderOperationStatus.Success; - - protected override Task> ValidateCreateAsync(PathContainer container) - { - if (container.Name.ContainsAny(Path.GetInvalidFileNameChars())) - { - return Task.FromResult(Attempt.Fail(ScriptFolderOperationStatus.InvalidName)); - } - - if (_scriptRepository.FolderExists(container.Path)) - { - return Task.FromResult(Attempt.Fail(ScriptFolderOperationStatus.AlreadyExists)); - } - - if (string.IsNullOrWhiteSpace(container.ParentPath) is false && - _scriptRepository.FolderExists(container.ParentPath) is false) - { - return Task.FromResult(Attempt.Fail(ScriptFolderOperationStatus.ParentNotFound)); - } - - return Task.FromResult(Attempt.Succeed(ScriptFolderOperationStatus.Success)); - } - - protected override Task> ValidateDeleteAsync(string path) - { - if(_scriptRepository.FolderExists(path) is false) - { - return Task.FromResult(Attempt.Fail(ScriptFolderOperationStatus.NotFound)); - } - - if (_scriptRepository.FolderHasContent(path)) - { - return Task.FromResult(Attempt.Fail(ScriptFolderOperationStatus.NotEmpty)); - } - - return Task.FromResult(Attempt.Succeed(ScriptFolderOperationStatus.Success)); - } -} diff --git a/src/Umbraco.Core/Services/ScriptService.cs b/src/Umbraco.Core/Services/ScriptService.cs index 9ee922e74b..e64822c1e1 100644 --- a/src/Umbraco.Core/Services/ScriptService.cs +++ b/src/Umbraco.Core/Services/ScriptService.cs @@ -8,181 +8,68 @@ using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.Services; -public class ScriptService : FileServiceBase, IScriptService +public class ScriptService : FileServiceOperationBase, IScriptService { - private readonly IAuditRepository _auditRepository; - private readonly IUserIdKeyResolver _userIdKeyResolver; - private readonly ILogger _logger; - - protected override string[] AllowedFileExtensions { get; } = { ".js" }; - public ScriptService( ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IScriptRepository scriptRepository, - IAuditRepository auditRepository, + IScriptRepository repository, + ILogger logger, IUserIdKeyResolver userIdKeyResolver, - ILogger logger) - : base(provider, loggerFactory, eventMessagesFactory, scriptRepository) + IAuditRepository auditRepository) + : base(provider, loggerFactory, eventMessagesFactory, repository, logger, userIdKeyResolver, auditRepository) { - _auditRepository = auditRepository; - _userIdKeyResolver = userIdKeyResolver; - _logger = logger; } - /// - public async Task DeleteAsync(string path, Guid userKey) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); + protected override string[] AllowedFileExtensions { get; } = { ".js" }; - IScript? script = Repository.Get(path); - if (script is null) - { - return ScriptOperationStatus.NotFound; - } + protected override ScriptOperationStatus Success => ScriptOperationStatus.Success; - EventMessages eventMessages = EventMessagesFactory.Get(); + protected override ScriptOperationStatus NotFound => ScriptOperationStatus.NotFound; - var deletingNotification = new ScriptDeletingNotification(script, eventMessages); - if (await scope.Notifications.PublishCancelableAsync(deletingNotification)) - { - return ScriptOperationStatus.CancelledByNotification; - } + protected override ScriptOperationStatus CancelledByNotification => ScriptOperationStatus.CancelledByNotification; - Repository.Delete(script); + protected override ScriptOperationStatus PathTooLong => ScriptOperationStatus.PathTooLong; - scope.Notifications.Publish( - new ScriptDeletedNotification(script, eventMessages).WithStateFrom(deletingNotification)); + protected override ScriptOperationStatus AlreadyExists => ScriptOperationStatus.AlreadyExists; - await AuditAsync(AuditType.Delete, userKey); + protected override ScriptOperationStatus ParentNotFound => ScriptOperationStatus.ParentNotFound; - scope.Complete(); - return ScriptOperationStatus.Success; - } + protected override ScriptOperationStatus InvalidName => ScriptOperationStatus.InvalidName; + + protected override ScriptOperationStatus InvalidFileExtension => ScriptOperationStatus.InvalidFileExtension; + + protected override string EntityType => "Script"; + + protected override ScriptSavingNotification SavingNotification(IScript target, EventMessages messages) + => new(target, messages); + + protected override ScriptSavedNotification SavedNotification(IScript target, EventMessages messages) + => new(target, messages); + + protected override ScriptDeletingNotification DeletingNotification(IScript target, EventMessages messages) + => new(target, messages); + + protected override ScriptDeletedNotification DeletedNotification(IScript target, EventMessages messages) + => new(target, messages); + + protected override IScript CreateEntity(string path, string? content) + => new Script(path) { Content = content }; /// public async Task> CreateAsync(ScriptCreateModel createModel, Guid userKey) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); - - try - { - ScriptOperationStatus validationResult = await ValidateCreateAsync(createModel); - if (validationResult is not ScriptOperationStatus.Success) - { - return Attempt.FailWithStatus(validationResult, null); - } - } - catch (PathTooLongException exception) - { - _logger.LogError(exception, "The script path was too long"); - return Attempt.FailWithStatus(ScriptOperationStatus.PathTooLong, null); - } - - var script = new Script(createModel.FilePath) { Content = createModel.Content }; - - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new ScriptSavingNotification(script, eventMessages); - if (await scope.Notifications.PublishCancelableAsync(savingNotification)) - { - return Attempt.FailWithStatus(ScriptOperationStatus.CancelledByNotification, null); - } - - Repository.Save(script); - - scope.Notifications.Publish(new ScriptSavedNotification(script, eventMessages).WithStateFrom(savingNotification)); - await AuditAsync(AuditType.Save, userKey); - - scope.Complete(); - return Attempt.SucceedWithStatus(ScriptOperationStatus.Success, script); - } - - private Task ValidateCreateAsync(ScriptCreateModel createModel) - { - if (Repository.Exists(createModel.FilePath)) - { - return Task.FromResult(ScriptOperationStatus.AlreadyExists); - } - - if (IsRootPath(createModel.ParentPath) is false && - Repository.FolderExists(createModel.ParentPath!) is false) - { - return Task.FromResult(ScriptOperationStatus.ParentNotFound); - } - - if (HasValidFileName(createModel.Name) is false) - { - return Task.FromResult(ScriptOperationStatus.InvalidName); - } - - if (HasValidFileExtension(createModel.FilePath) is false) - { - return Task.FromResult(ScriptOperationStatus.InvalidFileExtension); - } - - return Task.FromResult(ScriptOperationStatus.Success); - } + => await HandleCreateAsync(createModel.Name, createModel.ParentPath, createModel.Content, userKey); /// - public async Task> UpdateAsync(ScriptUpdateModel updateModel, Guid userKey) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); - IScript? script = Repository.Get(updateModel.ExistingPath); + public async Task> UpdateAsync(string path, ScriptUpdateModel updateModel, Guid userKey) + => await HandleUpdateAsync(path, updateModel.Content, userKey); - if (script is null) - { - return Attempt.FailWithStatus(ScriptOperationStatus.NotFound, null); - } + /// + public async Task DeleteAsync(string path, Guid userKey) + => await HandleDeleteAsync(path, userKey); - ScriptOperationStatus validationResult = ValidateUpdate(updateModel); - if (validationResult is not ScriptOperationStatus.Success) - { - return Attempt.FailWithStatus(validationResult, null); - } - - script.Content = updateModel.Content; - if (script.Name != updateModel.Name) - { - // Name has been updated, so we need to update the path as well - var newPath = script.Path.Replace(script.Name!, updateModel.Name); - script.Path = newPath; - } - - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new ScriptSavingNotification(script, eventMessages); - if (await scope.Notifications.PublishCancelableAsync(savingNotification)) - { - return Attempt.FailWithStatus(ScriptOperationStatus.CancelledByNotification, null); - } - - Repository.Save(script); - scope.Notifications.Publish(new ScriptSavedNotification(script, eventMessages).WithStateFrom(savingNotification)); - - await AuditAsync(AuditType.Save, userKey); - - scope.Complete(); - return Attempt.SucceedWithStatus(ScriptOperationStatus.Success, script); - } - - private ScriptOperationStatus ValidateUpdate(ScriptUpdateModel updateModel) - { - if (HasValidFileExtension(updateModel.Name) is false) - { - return ScriptOperationStatus.InvalidFileExtension; - } - - if (HasValidFileName(updateModel.Name) is false) - { - return ScriptOperationStatus.InvalidName; - } - - return ScriptOperationStatus.Success; - } - - private async Task AuditAsync(AuditType type, Guid userKey) - { - int userId = await _userIdKeyResolver.GetAsync(userKey); - _auditRepository.Save(new AuditItem(-1, type, userId, "Script")); - } + /// + public async Task> RenameAsync(string path, ScriptRenameModel renameModel, Guid userKey) + => await HandleRenameAsync(path, renameModel.Name, userKey); } diff --git a/src/Umbraco.Core/Services/StylesheetFolderService.cs b/src/Umbraco.Core/Services/StylesheetFolderService.cs deleted file mode 100644 index bcbd51ca9c..0000000000 --- a/src/Umbraco.Core/Services/StylesheetFolderService.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Persistence.Repositories; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services.OperationStatus; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Services; - -public class StylesheetFolderService : PathFolderServiceBase, IStylesheetFolderService -{ - private readonly IStylesheetRepository _stylesheetRepository; - - public StylesheetFolderService( - ICoreScopeProvider provider, - ILoggerFactory loggerFactory, - IEventMessagesFactory eventMessagesFactory, - IStylesheetRepository stylesheetRepository) - : base(provider, loggerFactory, eventMessagesFactory) => - _stylesheetRepository = stylesheetRepository; - - protected override IStylesheetRepository Repository => _stylesheetRepository; - - protected override StylesheetFolderOperationStatus SuccessStatus => StylesheetFolderOperationStatus.Success; - - protected override Task> ValidateCreateAsync(PathContainer container) - { - if (container.Name.ContainsAny(Path.GetInvalidFileNameChars())) - { - return Task.FromResult(Attempt.Fail(StylesheetFolderOperationStatus.InvalidName)); - } - - if (_stylesheetRepository.FolderExists(container.Path)) - { - return Task.FromResult(Attempt.Fail(StylesheetFolderOperationStatus.AlreadyExists)); - } - - if (string.IsNullOrWhiteSpace(container.ParentPath) is false && - _stylesheetRepository.FolderExists(container.ParentPath) is false) - { - return Task.FromResult(Attempt.Fail(StylesheetFolderOperationStatus.ParentNotFound)); - } - - return Task.FromResult(Attempt.Succeed(StylesheetFolderOperationStatus.Success)); - } - - protected override Task> ValidateDeleteAsync(string path) - { - if(_stylesheetRepository.FolderExists(path) is false) - { - return Task.FromResult(Attempt.Fail(StylesheetFolderOperationStatus.NotFound)); - } - - if (_stylesheetRepository.FolderHasContent(path)) - { - return Task.FromResult(Attempt.Fail(StylesheetFolderOperationStatus.NotEmpty)); - } - - return Task.FromResult(Attempt.Succeed(StylesheetFolderOperationStatus.Success)); - } -} diff --git a/src/Umbraco.Core/Services/StylesheetService.cs b/src/Umbraco.Core/Services/StylesheetService.cs index 821211cde0..08bab2a84a 100644 --- a/src/Umbraco.Core/Services/StylesheetService.cs +++ b/src/Umbraco.Core/Services/StylesheetService.cs @@ -8,179 +8,68 @@ using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Core.Services; -public class StylesheetService : FileServiceBase, IStylesheetService +public class StylesheetService : FileServiceOperationBase, IStylesheetService { - private readonly ILogger _logger; - private readonly IUserIdKeyResolver _userIdKeyResolver; - private readonly IAuditRepository _auditRepository; - - protected override string[] AllowedFileExtensions { get; } = { ".css" }; - public StylesheetService( ICoreScopeProvider provider, ILoggerFactory loggerFactory, IEventMessagesFactory eventMessagesFactory, - IStylesheetRepository stylesheetRepository, + IStylesheetRepository repository, ILogger logger, IUserIdKeyResolver userIdKeyResolver, IAuditRepository auditRepository) - : base(provider, loggerFactory, eventMessagesFactory, stylesheetRepository) + : base(provider, loggerFactory, eventMessagesFactory, repository, logger, userIdKeyResolver, auditRepository) { - _logger = logger; - _userIdKeyResolver = userIdKeyResolver; - _auditRepository = auditRepository; } - /// - public async Task DeleteAsync(string path, Guid userKey) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); + protected override string[] AllowedFileExtensions { get; } = { ".css" }; - IStylesheet? stylesheet = Repository.Get(path); - if (stylesheet is null) - { - return StylesheetOperationStatus.NotFound; - } + protected override StylesheetOperationStatus Success => StylesheetOperationStatus.Success; - EventMessages eventMessages = EventMessagesFactory.Get(); - var deletingNotification = new StylesheetDeletingNotification(stylesheet, eventMessages); - if (await scope.Notifications.PublishCancelableAsync(deletingNotification)) - { - return StylesheetOperationStatus.CancelledByNotification; - } + protected override StylesheetOperationStatus NotFound => StylesheetOperationStatus.NotFound; - Repository.Delete(stylesheet); + protected override StylesheetOperationStatus CancelledByNotification => StylesheetOperationStatus.CancelledByNotification; - scope.Notifications.Publish(new StylesheetDeletedNotification(stylesheet, eventMessages).WithStateFrom(deletingNotification)); - await AuditAsync(AuditType.Delete, userKey); + protected override StylesheetOperationStatus PathTooLong => StylesheetOperationStatus.PathTooLong; - scope.Complete(); - return StylesheetOperationStatus.Success; - } + protected override StylesheetOperationStatus AlreadyExists => StylesheetOperationStatus.AlreadyExists; + + protected override StylesheetOperationStatus ParentNotFound => StylesheetOperationStatus.ParentNotFound; + + protected override StylesheetOperationStatus InvalidName => StylesheetOperationStatus.InvalidName; + + protected override StylesheetOperationStatus InvalidFileExtension => StylesheetOperationStatus.InvalidFileExtension; + + protected override string EntityType => "Stylesheet"; + + protected override StylesheetSavingNotification SavingNotification(IStylesheet target, EventMessages messages) + => new(target, messages); + + protected override StylesheetSavedNotification SavedNotification(IStylesheet target, EventMessages messages) + => new(target, messages); + + protected override StylesheetDeletingNotification DeletingNotification(IStylesheet target, EventMessages messages) + => new(target, messages); + + protected override StylesheetDeletedNotification DeletedNotification(IStylesheet target, EventMessages messages) + => new(target, messages); + + protected override IStylesheet CreateEntity(string path, string? content) + => new Stylesheet(path) { Content = content }; /// public async Task> CreateAsync(StylesheetCreateModel createModel, Guid userKey) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); - - try - { - StylesheetOperationStatus validationResult = ValidateCreate(createModel); - if (validationResult is not StylesheetOperationStatus.Success) - { - return Attempt.FailWithStatus(validationResult, null); - } - } - catch (PathTooLongException exception) - { - _logger.LogError(exception, "The stylesheet path was too long"); - return Attempt.FailWithStatus(StylesheetOperationStatus.PathTooLong, null); - } - - var stylesheet = new Stylesheet(createModel.FilePath) { Content = createModel.Content }; - - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new StylesheetSavingNotification(stylesheet, eventMessages); - if (await scope.Notifications.PublishCancelableAsync(savingNotification)) - { - return Attempt.FailWithStatus(StylesheetOperationStatus.CancelledByNotification, null); - } - - Repository.Save(stylesheet); - - scope.Notifications.Publish(new StylesheetSavedNotification(stylesheet, eventMessages).WithStateFrom(savingNotification)); - await AuditAsync(AuditType.Save, userKey); - - scope.Complete(); - return Attempt.SucceedWithStatus(StylesheetOperationStatus.Success, stylesheet); - } - - private StylesheetOperationStatus ValidateCreate(StylesheetCreateModel createModel) - { - if (Repository.Exists(createModel.FilePath)) - { - return StylesheetOperationStatus.AlreadyExists; - } - - if (IsRootPath(createModel.ParentPath) is false - && Repository.FolderExists(createModel.ParentPath!) is false) - { - return StylesheetOperationStatus.ParentNotFound; - } - - if (HasValidFileName(createModel.Name) is false) - { - return StylesheetOperationStatus.InvalidName; - } - - if (HasValidFileExtension(createModel.FilePath) is false) - { - return StylesheetOperationStatus.InvalidFileExtension; - } - - return StylesheetOperationStatus.Success; - } + => await HandleCreateAsync(createModel.Name, createModel.ParentPath, createModel.Content, userKey); /// - public async Task> UpdateAsync(StylesheetUpdateModel updateModel, Guid userKey) - { - using ICoreScope scope = ScopeProvider.CreateCoreScope(); + public async Task> UpdateAsync(string path, StylesheetUpdateModel updateModel, Guid userKey) + => await HandleUpdateAsync(path, updateModel.Content, userKey); - IStylesheet? stylesheet = Repository.Get(updateModel.ExistingPath); + /// + public async Task DeleteAsync(string path, Guid userKey) + => await HandleDeleteAsync(path, userKey); - if (stylesheet is null) - { - return Attempt.FailWithStatus(StylesheetOperationStatus.NotFound, null); - } - - StylesheetOperationStatus validationResult = ValidateUpdate(updateModel); - if (validationResult is not StylesheetOperationStatus.Success) - { - return Attempt.FailWithStatus(validationResult, null); - } - - stylesheet.Content = updateModel.Content; - if (stylesheet.Name != updateModel.Name) - { - // Name has been updated, so we need to update the path as well - var newPath = stylesheet.Path.Replace(stylesheet.Name!, updateModel.Name); - stylesheet.Path = newPath; - } - - EventMessages eventMessages = EventMessagesFactory.Get(); - var savingNotification = new StylesheetSavingNotification(stylesheet, eventMessages); - if (await scope.Notifications.PublishCancelableAsync(savingNotification)) - { - return Attempt.FailWithStatus(StylesheetOperationStatus.CancelledByNotification, null); - } - - Repository.Save(stylesheet); - - scope.Notifications.Publish(new StylesheetSavedNotification(stylesheet, eventMessages).WithStateFrom(savingNotification)); - await AuditAsync(AuditType.Save, userKey); - - scope.Complete(); - return Attempt.SucceedWithStatus(StylesheetOperationStatus.Success, stylesheet); - } - - private StylesheetOperationStatus ValidateUpdate(StylesheetUpdateModel updateModel) - { - if (HasValidFileExtension(updateModel.Name) is false) - { - return StylesheetOperationStatus.InvalidFileExtension; - } - - if (HasValidFileName(updateModel.Name) is false) - { - return StylesheetOperationStatus.InvalidName; - } - - return StylesheetOperationStatus.Success; - } - - private async Task AuditAsync(AuditType type, Guid userKey) - { - var userId = await _userIdKeyResolver.GetAsync(userKey); - _auditRepository.Save(new AuditItem(-1, type, userId, "Stylesheet")); - } + /// + public async Task> RenameAsync(string path, StylesheetRenameModel renameModel, Guid userKey) + => await HandleRenameAsync(path, renameModel.Name, userKey); } diff --git a/src/Umbraco.Core/Snippets/ISnippet.cs b/src/Umbraco.Core/Snippets/ISnippet.cs deleted file mode 100644 index 67c9bf9e7f..0000000000 --- a/src/Umbraco.Core/Snippets/ISnippet.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Umbraco.Cms.Core.Snippets -{ - /// - /// Defines a partial view macro snippet. - /// - public interface ISnippet - { - /// - /// Gets the name of the snippet. - /// - string Name { get; } - - /// - /// Gets the content of the snippet. - /// - string Content { get; } - } -} diff --git a/src/Umbraco.Core/Snippets/PartialViewMacroSnippetCollection.cs b/src/Umbraco.Core/Snippets/PartialViewMacroSnippetCollection.cs deleted file mode 100644 index b2fb79553c..0000000000 --- a/src/Umbraco.Core/Snippets/PartialViewMacroSnippetCollection.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Text.RegularExpressions; -using Umbraco.Cms.Core.Composing; -using Umbraco.Extensions; - -namespace Umbraco.Cms.Core.Snippets -{ - /// - /// The collection of partial view macro snippets. - /// - public class PartialViewMacroSnippetCollection : BuilderCollectionBase - { - public PartialViewMacroSnippetCollection(Func> items) : base(items) - { - } - - /// - /// Gets the partial view macro snippet names. - /// - /// The names of all partial view macro snippets. - public IEnumerable GetNames() - { - var snippetNames = this.Select(x => Path.GetFileNameWithoutExtension(x.Name)).ToArray(); - - // Ensure the ones that are called 'Empty' are at the top - var empty = snippetNames.Where(x => Path.GetFileName(x)?.InvariantStartsWith("Empty") ?? false) - .OrderBy(x => x?.Length).ToArray(); - - return empty.Union(snippetNames.Except(empty)).WhereNotNull(); - } - - /// - /// Gets the content of a partial view macro snippet as a string. - /// - /// The name of the snippet. - /// The content of the partial view macro. - public string GetContentFromName(string snippetName) - { - if (snippetName.IsNullOrWhiteSpace()) - { - throw new ArgumentNullException(nameof(snippetName)); - } - - string partialViewMacroHeader = "@inherits Umbraco.Cms.Web.Common.Macros.PartialViewMacroPage"; - - var snippet = this.Where(x => x.Name.Equals(snippetName + ".cshtml")).FirstOrDefault(); - - // Try and get the snippet path - if (snippet is null) - { - throw new InvalidOperationException("Could not load snippet with name " + snippetName); - } - - // Strip the @inherits if it's there - var snippetContent = StripPartialViewHeader(snippet.Content); - - var content = $"{partialViewMacroHeader}{Environment.NewLine}{snippetContent}"; - return content; - } - - private string StripPartialViewHeader(string contents) - { - var headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline); - return headerMatch.Replace(contents, string.Empty); - } - } -} diff --git a/src/Umbraco.Core/Snippets/PartialViewMacroSnippetCollectionBuilder.cs b/src/Umbraco.Core/Snippets/PartialViewMacroSnippetCollectionBuilder.cs deleted file mode 100644 index cf737368ce..0000000000 --- a/src/Umbraco.Core/Snippets/PartialViewMacroSnippetCollectionBuilder.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.Hosting; -using Umbraco.Cms.Core.Composing; -using Umbraco.Cms.Core.Extensions; - -namespace Umbraco.Cms.Core.Snippets -{ - /// - /// The partial view macro snippet collection builder. - /// - public class PartialViewMacroSnippetCollectionBuilder : LazyCollectionBuilderBase - { - protected override PartialViewMacroSnippetCollectionBuilder This => this; - - protected override IEnumerable CreateItems(IServiceProvider factory) - { - var hostEnvironment = factory.GetRequiredService(); - - var embeddedSnippets = new List(base.CreateItems(factory)); - var snippetProvider = new EmbeddedFileProvider(typeof(IAssemblyProvider).Assembly, "Umbraco.Cms.Core.EmbeddedResources.Snippets"); - var embeddedFiles = snippetProvider.GetDirectoryContents(string.Empty) - .Where(x => !x.IsDirectory && x.Name.EndsWith(".cshtml")); - - foreach (var file in embeddedFiles) - { - using var stream = new StreamReader(file.CreateReadStream()); - embeddedSnippets.Add(new Snippet(file.Name, stream.ReadToEnd().Trim())); - } - - var customSnippetsDir = new DirectoryInfo(hostEnvironment.MapPathContentRoot($"{Constants.SystemDirectories.Umbraco}/PartialViewMacros/Templates")); - if (!customSnippetsDir.Exists) - { - return embeddedSnippets; - } - - var customSnippets = customSnippetsDir.GetFiles().Select(f => new Snippet(f.Name, File.ReadAllText(f.FullName))); - var allSnippets = Merge(embeddedSnippets, customSnippets); - - return allSnippets; - } - - private IEnumerable Merge(IEnumerable embeddedSnippets, IEnumerable customSnippets) - { - var allSnippets = embeddedSnippets.Concat(customSnippets); - - var duplicates = allSnippets.GroupBy(s => s.Name) - .Where(gr => gr.Count() > 1) // Finds the snippets with the same name - .Select(s => s.First()); // Takes the first element from a grouping, which is the embeded snippet with that same name, - // since the physical snippet files are placed after the embedded ones in the all snippets colleciton - - // Remove any embedded snippets if a physical file with the same name can be found - return allSnippets.Except(duplicates); - } - } -} diff --git a/src/Umbraco.Core/Snippets/PartialViewSnippet.cs b/src/Umbraco.Core/Snippets/PartialViewSnippet.cs index deec00ed1a..87a1e62bfb 100644 --- a/src/Umbraco.Core/Snippets/PartialViewSnippet.cs +++ b/src/Umbraco.Core/Snippets/PartialViewSnippet.cs @@ -1,8 +1,16 @@ namespace Umbraco.Cms.Core.Snippets; -public class PartialViewSnippet : Snippet +/// +/// Defines a partial view snippet. +/// +public class PartialViewSnippet : PartialViewSnippetSlim { - public PartialViewSnippet(string name, string content) : base(name, content) - { - } + public PartialViewSnippet(string id, string name, string content) + : base(id, name) + => Content = content; + + /// + /// Gets the content of the snippet. + /// + public string Content { get; } } diff --git a/src/Umbraco.Core/Snippets/PartialViewSnippetCollection.cs b/src/Umbraco.Core/Snippets/PartialViewSnippetCollection.cs index f20e0fd3e8..a49c2c897a 100644 --- a/src/Umbraco.Core/Snippets/PartialViewSnippetCollection.cs +++ b/src/Umbraco.Core/Snippets/PartialViewSnippetCollection.cs @@ -1,72 +1,11 @@ -using System.Text.RegularExpressions; using Umbraco.Cms.Core.Composing; -using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Snippets +namespace Umbraco.Cms.Core.Snippets; + +public class PartialViewSnippetCollection : BuilderCollectionBase { - /// - /// The collection of partial view snippets. - /// - public class PartialViewSnippetCollection : BuilderCollectionBase + public PartialViewSnippetCollection(Func> items) + : base(items) { - public PartialViewSnippetCollection(Func> items) : base(items) - { - } - - /// - /// Gets the partial view snippet names. - /// - /// The names of all partial view snippets. - public IEnumerable GetNames() - { - var snippetNames = this.Select(x => Path.GetFileNameWithoutExtension(x.Name)).ToArray(); - - // Ensure the ones that are called 'Empty' are at the top - var empty = snippetNames.Where(x => Path.GetFileName(x)?.InvariantStartsWith("Empty") ?? false) - .OrderBy(x => x?.Length).ToArray(); - - return empty.Union(snippetNames.Except(empty)).WhereNotNull(); - } - - /// - /// Gets the content of a partial view snippet as a string. - /// - /// The name of the snippet. - /// The content of the partial view. - public string GetContentFromName(string snippetName) - { - if (snippetName.IsNullOrWhiteSpace()) - { - throw new ArgumentNullException(nameof(snippetName)); - } - - string partialViewHeader = "@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage"; - - ISnippet? snippet = this.Where(x => x.Name.Equals(snippetName + ".cshtml")).FirstOrDefault(); - - // Try and get the snippet path - if (snippet is null) - { - throw new InvalidOperationException("Could not load snippet with name " + snippetName); - } - - var snippetContent = CleanUpContents(snippet.Content); - - var content = $"{partialViewHeader}{Environment.NewLine}{snippetContent}"; - return content; - } - - private static string CleanUpContents(string content) - { - // Strip the @inherits if it's there - var headerMatch = new Regex("^@inherits\\s+?.*$", RegexOptions.Multiline); - var newContent = headerMatch.Replace(content, string.Empty); - - return newContent - .Replace("Model.Content.", "Model.") - .Replace("(Model.Content)", "(Model)") - .Replace("Model?.Content.", "Model.") - .Replace("(Model?.Content)", "(Model)"); - } } } diff --git a/src/Umbraco.Core/Snippets/PartialViewSnippetCollectionBuilder.cs b/src/Umbraco.Core/Snippets/PartialViewSnippetCollectionBuilder.cs index 730e984d33..90f5551ea8 100644 --- a/src/Umbraco.Core/Snippets/PartialViewSnippetCollectionBuilder.cs +++ b/src/Umbraco.Core/Snippets/PartialViewSnippetCollectionBuilder.cs @@ -1,42 +1,69 @@ +using System.Text.RegularExpressions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; -namespace Umbraco.Cms.Core.Snippets +namespace Umbraco.Cms.Core.Snippets; + +/// +/// The partial view snippet collection builder. +/// +public partial class PartialViewSnippetCollectionBuilder : LazyCollectionBuilderBase { - /// - /// The partial view snippet collection builder. - /// - public class PartialViewSnippetCollectionBuilder : LazyCollectionBuilderBase + protected override PartialViewSnippetCollectionBuilder This => this; + + protected override IEnumerable CreateItems(IServiceProvider factory) { - protected override PartialViewSnippetCollectionBuilder This => this; + var embeddedSnippets = new List(base.CreateItems(factory)); - protected override IEnumerable CreateItems(IServiceProvider factory) + // Ignore these + var filterNames = new List { - var embeddedSnippets = new List(base.CreateItems(factory)); + "Gallery", + "ListChildPagesFromChangeableSource", + "ListChildPagesOrderedByProperty", + "ListImagesFromMediaFolder" + }; - // Ignore these - var filterNames = new List + var snippetProvider = new EmbeddedFileProvider(typeof(IAssemblyProvider).Assembly, "Umbraco.Cms.Core.EmbeddedResources.Snippets"); + IEnumerable embeddedFiles = snippetProvider.GetDirectoryContents(string.Empty) + .Where(x => !x.IsDirectory && x.Name.EndsWith(".cshtml")); + + IShortStringHelper shortStringHelper = factory.GetRequiredService(); + foreach (IFileInfo file in embeddedFiles) + { + var id = Path.GetFileNameWithoutExtension(file.Name); + if (filterNames.Contains(id)) { - "Gallery", - "ListChildPagesFromChangeableSource", - "ListChildPagesOrderedByProperty", - "ListImagesFromMediaFolder" - }; - - var snippetProvider = new EmbeddedFileProvider(typeof(IAssemblyProvider).Assembly, "Umbraco.Cms.Core.EmbeddedResources.Snippets"); - var embeddedFiles = snippetProvider.GetDirectoryContents(string.Empty) - .Where(x => !x.IsDirectory && x.Name.EndsWith(".cshtml")); - - foreach (var file in embeddedFiles) - { - if (!filterNames.Contains(Path.GetFileNameWithoutExtension(file.Name))) - { - using var stream = new StreamReader(file.CreateReadStream()); - embeddedSnippets.Add(new Snippet(file.Name, stream.ReadToEnd().Trim())); - } + continue; } - return embeddedSnippets; + var name = id.SplitPascalCasing(shortStringHelper).ToFirstUpperInvariant(); + using var stream = new StreamReader(file.CreateReadStream()); + var content = CleanUpSnippetContent(stream.ReadToEnd().Trim()); + embeddedSnippets.Add(new PartialViewSnippet(id, name, content)); } + + return embeddedSnippets; } + + private string CleanUpSnippetContent(string content) + { + const string partialViewHeader = "@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage"; + + // Strip the @inherits if it's there + Regex headerMatch = HeaderRegex(); + var newContent = headerMatch.Replace(content, string.Empty) + .Replace("Model.Content.", "Model.") + .Replace("(Model.Content)", "(Model)") + .Replace("Model?.Content.", "Model.") + .Replace("(Model?.Content)", "(Model)"); + + return $"{partialViewHeader}{Environment.NewLine}{newContent}"; + } + + [GeneratedRegex("^@inherits\\s+?.*$", RegexOptions.Multiline)] + private static partial Regex HeaderRegex(); } diff --git a/src/Umbraco.Core/Snippets/PartialViewSnippetSlim.cs b/src/Umbraco.Core/Snippets/PartialViewSnippetSlim.cs new file mode 100644 index 0000000000..6cf28522d9 --- /dev/null +++ b/src/Umbraco.Core/Snippets/PartialViewSnippetSlim.cs @@ -0,0 +1,23 @@ +namespace Umbraco.Cms.Core.Snippets; + +/// +/// A lightweight representation of a partial view snippet (i.e. without content). +/// +public class PartialViewSnippetSlim +{ + public PartialViewSnippetSlim(string id, string name) + { + Id = id; + Name = name; + } + + /// + /// Gets the ID of the snippet. + /// + public string Id { get; } + + /// + /// Gets the name of the snippet. + /// + public string Name { get; } +} diff --git a/src/Umbraco.Core/Snippets/Snippet.cs b/src/Umbraco.Core/Snippets/Snippet.cs deleted file mode 100644 index 290d24bdf7..0000000000 --- a/src/Umbraco.Core/Snippets/Snippet.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Umbraco.Cms.Core.Snippets -{ - public class Snippet : ISnippet - { - public string Name { get; } - - public string Content { get; } - - public Snippet(string name, string content) - { - Name = name; - Content = content; - } - } -} diff --git a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs index e2eb3df7a4..f3d14a6fe2 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs @@ -3,6 +3,7 @@ using Umbraco.Extensions; namespace Umbraco.Cms.Core.Strings.Css; +// FIXME: remove this class and all usage of it (moved to the client in V14) public class StylesheetHelper { private const string RuleRegexFormat = diff --git a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs index 0e4cc3de6d..28116570eb 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs @@ -45,8 +45,6 @@ public class CodeFileController : BackOfficeNotificationsController private readonly ILocalizedTextService _localizedTextService; private readonly IShortStringHelper _shortStringHelper; private readonly IUmbracoMapper _umbracoMapper; - private readonly PartialViewSnippetCollection _partialViewSnippetCollection; - private readonly PartialViewMacroSnippetCollection _partialViewMacroSnippetCollection; [ActivatorUtilitiesConstructor] public CodeFileController( @@ -57,9 +55,7 @@ public class CodeFileController : BackOfficeNotificationsController ILocalizedTextService localizedTextService, IUmbracoMapper umbracoMapper, IShortStringHelper shortStringHelper, - IOptionsSnapshot globalSettings, - PartialViewSnippetCollection partialViewSnippetCollection, - PartialViewMacroSnippetCollection partialViewMacroSnippetCollection) + IOptionsSnapshot globalSettings) { _hostingEnvironment = hostingEnvironment; _fileSystems = fileSystems; @@ -69,31 +65,6 @@ public class CodeFileController : BackOfficeNotificationsController _umbracoMapper = umbracoMapper; _shortStringHelper = shortStringHelper; _globalSettings = globalSettings.Value; - _partialViewSnippetCollection = partialViewSnippetCollection; - _partialViewMacroSnippetCollection = partialViewMacroSnippetCollection; - } - - [Obsolete("Use ctor will all params. Scheduled for removal in V12.")] - public CodeFileController( - IHostingEnvironment hostingEnvironment, - FileSystems fileSystems, - IFileService fileService, - IBackOfficeSecurityAccessor backOfficeSecurityAccessor, - ILocalizedTextService localizedTextService, - IUmbracoMapper umbracoMapper, - IShortStringHelper shortStringHelper, - IOptionsSnapshot globalSettings) : this( - hostingEnvironment, - fileSystems, - fileService, - backOfficeSecurityAccessor, - localizedTextService, - umbracoMapper, - shortStringHelper, - globalSettings, - StaticServiceProvider.Instance.GetRequiredService(), - StaticServiceProvider.Instance.GetRequiredService()) - { } /// @@ -335,10 +306,10 @@ public class CodeFileController : BackOfficeNotificationsController switch (type) { case Constants.Trees.PartialViews: - snippets = _partialViewSnippetCollection.GetNames(); + throw new NotImplementedException("Partial views snippets have been rewritten for V14 - no longer supported in the old back-office."); break; case Constants.Trees.PartialViewMacros: - snippets = _partialViewMacroSnippetCollection.GetNames(); + throw new NotSupportedException("Macros are not part of V14 :)"); break; default: return NotFound(); @@ -376,31 +347,9 @@ public class CodeFileController : BackOfficeNotificationsController switch (type) { case Constants.Trees.PartialViews: - codeFileDisplay = - _umbracoMapper.Map(new PartialView(PartialViewType.PartialView, string.Empty)); - if (codeFileDisplay is not null) - { - codeFileDisplay.VirtualPath = Constants.SystemDirectories.PartialViews; - if (snippetName.IsNullOrWhiteSpace() == false) - { - codeFileDisplay.Content = _partialViewSnippetCollection.GetContentFromName(snippetName!); - } - } - - break; + throw new NotImplementedException("Partial views snippets have been rewritten for V14 - no longer supported in the old back-office."); case Constants.Trees.PartialViewMacros: - codeFileDisplay = - _umbracoMapper.Map(new PartialView(PartialViewType.PartialViewMacro, string.Empty)); - if (codeFileDisplay is not null) - { - codeFileDisplay.VirtualPath = Constants.SystemDirectories.MacroPartials; - if (snippetName.IsNullOrWhiteSpace() == false) - { - codeFileDisplay.Content = _partialViewMacroSnippetCollection.GetContentFromName(snippetName!); - } - } - - break; + throw new NotSupportedException("Macros are not part of V14 :)"); case Constants.Trees.Scripts: codeFileDisplay = _umbracoMapper.Map(new Script(string.Empty)); if (codeFileDisplay is not null)