New backoffice: Add new codefile controllers and services (#14157)

* Add scaffolding

* Entities not Entitys

* Remove unececary obsoleted constructor

* Implement create script

* Return a simplified ScriptFile instead of IScript

That file abstraction seems way too bloated, containing a lot of stuff that's not relevant for files, such as IDs and keys.

* Use IScript instead of custom return model

* Add validation when creating script

* Add Get script endpoint

* Add response types

* Add Delete

* Throw if user key not found

* Remove unused maapper

* Add update endpoint

* Add Get by path

* Add create folder endpoint

* Don't pass performingUserId to folder creation

* Remove update folder

* Add delete folder endpoint

* Use specific ScriptFolderOperationStatus instead of ScriptOperationStatus

* Add OperationStatusResult

* Check folder for invalid name

* Check name for invalid characters

* Add partial view snippet endpoint

* Start working on CreatePartialView

* Add create partial view endpoint

* Retrieve key from audit method

* Add operation status results

* Add Get endpoint

* Return 201 when creating

* Add update partial view endpoint

* Add delete endpoint

* Add response types

* Add folder base implementation

* Add folder endpoints

* User property for allowed file extensions

* Rename async method to async

* Break snippet into endpoint in two

* Make content non-nullable

* Remove IService

* Add get by path

* Add viewmodels

* Add create and update models

* Add create stylesheet

* Add update endpoint

* Rename StylesheetControllerBase to StylesheetControllerBase

* Add stylesheet delete

* Rename controller bases

* Add stylesheet folders

* Add status results

* Add response types to folders

* Add richtext rules endpoints

* Add Get all endpoint

* Add get rules by path endpoint

* Aling validates so they're not async

These are private methods, so there's no reason to make them preemptively async

* Add template obsoletions to interface

* Add stream methods

This is evidently used by deploy 🤷

* Obsolete stylesheet operations

* Add get and getall across all services

* Obsolete script operations

* Obsolete old partial view methods

* Add some method docs

* Add compatibility suppression

* Update OpenApi.json

* Rename action

* formatting

* Fix import

* add expression body

* Invert if

* Move base on own line

* Rename file

* Rename to all

* Change to stylesheet instead of script

* Add Umbraco.Code.MapAll to map definitions

* Add comment about auditing

* use publish cancelable async

* use expression body

* formatting

* fix to use pattern matching

---------

Co-authored-by: Zeegaan <nge@umbraco.dk>
This commit is contained in:
Mole
2023-04-26 13:47:47 +02:00
committed by GitHub
parent 17fef47450
commit b411452f79
122 changed files with 4381 additions and 34 deletions

View File

@@ -0,0 +1,35 @@
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;
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<StylesheetOverviewResponseModel>), StatusCodes.Status200OK)]
public async Task<IActionResult> All(int skip = 0, int take = 100)
{
IStylesheet[] stylesheets = (await _stylesheetService.GetAllAsync()).ToArray();
List<StylesheetOverviewResponseModel> viewModels = _umbracoMapper.MapEnumerable<IStylesheet, StylesheetOverviewResponseModel>(stylesheets.Skip(skip).Take(take));
return Ok(new PagedViewModel<StylesheetOverviewResponseModel> { Items = viewModels, Total = stylesheets.Length });
}
}

View File

@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
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;
public class ByPathStylesheetController : StylesheetControllerBase
{
private readonly IStylesheetService _stylesheetService;
private readonly IUmbracoMapper _umbracoMapper;
public ByPathStylesheetController(
IStylesheetService stylesheetService,
IUmbracoMapper umbracoMapper)
{
_stylesheetService = stylesheetService;
_umbracoMapper = umbracoMapper;
}
[HttpGet]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(StylesheetResponseModel), StatusCodes.Status200OK)]
public async Task<IActionResult> ByPath(string path)
{
IStylesheet? stylesheet = await _stylesheetService.GetAsync(path);
if (stylesheet is null)
{
return NotFound();
}
StylesheetResponseModel? viewModel = _umbracoMapper.Map<StylesheetResponseModel>(stylesheet);
return Ok(viewModel);
}
}

View File

@@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
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;
public class CreateStylesheetController : StylesheetControllerBase
{
private readonly IStylesheetService _stylesheetService;
private readonly IUmbracoMapper _umbracoMapper;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
public CreateStylesheetController(
IStylesheetService stylesheetService,
IUmbracoMapper umbracoMapper,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_stylesheetService = stylesheetService;
_umbracoMapper = umbracoMapper;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status201Created)]
public async Task<IActionResult> Create(CreateStylesheetRequestModel requestModel)
{
StylesheetCreateModel createModel = _umbracoMapper.Map<StylesheetCreateModel>(requestModel)!;
Attempt<IStylesheet?, StylesheetOperationStatus> createAttempt = await _stylesheetService.CreateAsync(createModel, CurrentUserKey(_backOfficeSecurityAccessor));
return createAttempt.Success
? CreatedAtAction<ByPathStylesheetController>(controller => nameof(controller.ByPath), new { path = createAttempt.Result!.Path })
: StylesheetOperationStatusResult(createAttempt.Status);
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet;
public class DeleteStylesheetController : StylesheetControllerBase
{
private readonly IStylesheetService _stylesheetService;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
public DeleteStylesheetController(
IStylesheetService stylesheetService,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_stylesheetService = stylesheetService;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[HttpDelete]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Delete(string path)
{
StylesheetOperationStatus operationStatus = await _stylesheetService.DeleteAsync(path, CurrentUserKey(_backOfficeSecurityAccessor));
return operationStatus is StylesheetOperationStatus.Success
? Ok()
: StylesheetOperationStatusResult(operationStatus);
}
}

View File

@@ -0,0 +1,38 @@
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;
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<IActionResult> ExtractRichTextRules(ExtractRichTextStylesheetRulesRequestModel requestModel)
{
RichTextStylesheetData? model = _umbracoMapper.Map<RichTextStylesheetData>(requestModel);
IEnumerable<StylesheetRule> rules = await _richTextStylesheetService.ExtractRichTextRules(model!);
return Ok(new ExtractRichTextStylesheetRulesResponseModel
{
Rules = _umbracoMapper.MapEnumerable<StylesheetRule, RichTextRuleViewModel>(rules),
});
}
}

View File

@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Folder;
public class ByPathStylesheetFolderController : StylesheetFolderControllerBase
{
public ByPathStylesheetFolderController(IUmbracoMapper mapper, IStylesheetFolderService stylesheetFolderService) : base(mapper, stylesheetFolderService)
{
}
[HttpGet]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<IActionResult> ByPath(string path) => GetFolderAsync(path);
}

View File

@@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.Folder;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Folder;
public class CreateStylesheetFolderController : StylesheetFolderControllerBase
{
public CreateStylesheetFolderController(IUmbracoMapper mapper, IStylesheetFolderService stylesheetFolderService) : base(mapper, stylesheetFolderService)
{
}
[HttpPost]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<IActionResult> Create(CreatePathFolderRequestModel model) => CreateAsync(model);
}

View File

@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Services;
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Folder;
public class DeleteStylesheetFolderController : StylesheetFolderControllerBase
{
public DeleteStylesheetFolderController(IUmbracoMapper mapper, IStylesheetFolderService stylesheetFolderService) : base(mapper, stylesheetFolderService)
{
}
[HttpDelete]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<IActionResult> Delete(string path) => DeleteAsync(path);
}

View File

@@ -0,0 +1,62 @@
using Microsoft.AspNetCore.Http;
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;
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Folder;
[ApiVersion("1.0")]
[ApiController]
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Stylesheet}/folder")]
[ApiExplorerSettings(GroupName = "Stylesheet")]
public class StylesheetFolderControllerBase : PathFolderManagementControllerBase<StylesheetFolderOperationStatus>
{
private readonly IStylesheetFolderService _stylesheetFolderService;
public StylesheetFolderControllerBase(
IUmbracoMapper mapper,
IStylesheetFolderService stylesheetFolderService)
: base(mapper)
{
_stylesheetFolderService = stylesheetFolderService;
}
protected override Task<PathContainer?> GetContainerAsync(string path) => _stylesheetFolderService.GetAsync(path);
protected override Task<Attempt<PathContainer?, StylesheetFolderOperationStatus>> CreateContainerAsync(PathContainer container)
=> _stylesheetFolderService.CreateAsync(container);
protected override Task<Attempt<StylesheetFolderOperationStatus>> DeleteContainerAsync(string path)
=> _stylesheetFolderService.DeleteAsync(path);
protected override IActionResult OperationStatusResult(StylesheetFolderOperationStatus status) =>
status switch
{
StylesheetFolderOperationStatus.AlreadyExists => BadRequest(new ProblemDetailsBuilder()
.WithTitle("Folder already exists")
.WithDetail("The folder already exists")
.Build()),
StylesheetFolderOperationStatus.NotEmpty => BadRequest(new ProblemDetailsBuilder()
.WithTitle("Not empty")
.WithDetail("The folder is not empty and can therefore not be deleted.")
.Build()),
StylesheetFolderOperationStatus.NotFound => NotFound(new ProblemDetailsBuilder()
.WithTitle("Not found")
.WithDetail("The specified folder was not found.")
.Build()),
StylesheetFolderOperationStatus.ParentNotFound => NotFound(new ProblemDetailsBuilder()
.WithTitle("Parent not found")
.WithDetail("The parent folder was not found.")
.Build()),
StylesheetFolderOperationStatus.InvalidName => BadRequest(new ProblemDetailsBuilder()
.WithTitle("Invalid name")
.WithDetail("The name specified is not a valid name.")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown stylesheet folder operation status")
};
}

View File

@@ -0,0 +1,42 @@
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;
public class GetRichTextRulesByPath : StylesheetControllerBase
{
private readonly IRichTextStylesheetService _richTextStylesheetService;
private readonly IUmbracoMapper _umbracoMapper;
public GetRichTextRulesByPath(
IRichTextStylesheetService richTextStylesheetService,
IUmbracoMapper umbracoMapper)
{
_richTextStylesheetService = richTextStylesheetService;
_umbracoMapper = umbracoMapper;
}
[HttpGet("rich-text/rules")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(RichTextStylesheetRulesResponseModel), StatusCodes.Status200OK)]
public async Task<IActionResult> GetByPath(string path)
{
Attempt<IEnumerable<StylesheetRule>, StylesheetOperationStatus> rulesAttempt = await _richTextStylesheetService.GetRulesByPathAsync(path);
if (rulesAttempt.Success is false)
{
return StylesheetOperationStatusResult(rulesAttempt.Status);
}
return Ok(new RichTextStylesheetRulesResponseModel
{
Rules = _umbracoMapper.MapEnumerable<StylesheetRule, RichTextRuleViewModel>(rulesAttempt.Result)
});
}
}

View File

@@ -0,0 +1,37 @@
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;
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<IActionResult> InterpolateRichTextRules(InterpolateRichTextStylesheetRequestModel requestModel)
{
RichTextStylesheetData? model = _umbracoMapper.Map<RichTextStylesheetData>(requestModel);
var content = await _richTextStylesheetService.InterpolateRichTextRules(model!);
return Ok(new InterpolateRichTextStylesheetResponseModel
{
Content = content,
});
}
}

View File

@@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.Builders;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet;
[ApiVersion("1.0")]
[ApiController]
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Stylesheet}")]
[ApiExplorerSettings(GroupName = "Stylesheet")]
public class StylesheetControllerBase : ManagementApiControllerBase
{
protected IActionResult StylesheetOperationStatusResult(StylesheetOperationStatus status) =>
status switch
{
StylesheetOperationStatus.Success => Ok(),
StylesheetOperationStatus.AlreadyExists => BadRequest(new ProblemDetailsBuilder()
.WithTitle("Stylesheet already exists")
.WithDetail("A stylesheet with the same path already exists")
.Build()),
StylesheetOperationStatus.CancelledByNotification => BadRequest(new ProblemDetailsBuilder()
.WithTitle("Cancelled by notification")
.WithDetail("A stylesheet notification handler prevented the stylesheet operation.")
.Build()),
StylesheetOperationStatus.InvalidFileExtension => BadRequest(new ProblemDetailsBuilder()
.WithTitle("Invalid file extension")
.WithDetail("The file extension is not valid for a stylesheet.")
.Build()),
StylesheetOperationStatus.ParentNotFound => NotFound(new ProblemDetailsBuilder()
.WithTitle("Parent not found")
.WithDetail("The parent folder was not found.")
.Build()),
StylesheetOperationStatus.PathTooLong => BadRequest(new ProblemDetailsBuilder()
.WithTitle("Path too long")
.WithDetail("The file path is too long.")
.Build()),
StylesheetOperationStatus.NotFound => NotFound(new ProblemDetailsBuilder()
.WithTitle("Stylesheet not found")
.WithDetail("The stylesheet was not found.")
.Build()),
StylesheetOperationStatus.InvalidName => BadRequest(new ProblemDetailsBuilder()
.WithTitle("Invalid name")
.WithDetail("The stylesheet name is invalid.")
.Build()),
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown script operation status"),
};
}

View File

@@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
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;
public class UpdateStylesheetController : StylesheetControllerBase
{
private readonly IStylesheetService _stylesheetService;
private readonly IUmbracoMapper _umbracoMapper;
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
public UpdateStylesheetController(
IStylesheetService stylesheetService,
IUmbracoMapper umbracoMapper,
IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
{
_stylesheetService = stylesheetService;
_umbracoMapper = umbracoMapper;
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
}
[HttpPut]
[MapToApiVersion("1.0")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Update(UpdateStylesheetRequestModel requestModel)
{
StylesheetUpdateModel updateModel = _umbracoMapper.Map<StylesheetUpdateModel>(requestModel)!;
Attempt<IStylesheet?, StylesheetOperationStatus> updateAttempt = await _stylesheetService.UpdateAsync(updateModel, CurrentUserKey(_backOfficeSecurityAccessor));
return updateAttempt.Success
? Ok()
: StylesheetOperationStatusResult(updateAttempt.Status);
}
}