diff --git a/.gitignore b/.gitignore index 6a0c21d66f..f43d093009 100644 --- a/.gitignore +++ b/.gitignore @@ -68,7 +68,11 @@ preserve.belle /build/docs.zip /build/ui-docs.zip /build/csharp-docs.zip -/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/ +/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/backoffice +/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/assets +/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/js +/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/lib +/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/views # Environment specific data /src/Umbraco.Web.UI.Client/[Bb]uild/ diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index ff52d3c2aa..deccda508c 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -39,6 +39,7 @@ stages: ## Build ############################################### - stage: Build + variables: npm_config_cache: $(Pipeline.Workspace)/.npm_client jobs: @@ -47,6 +48,8 @@ stages: pool: vmImage: 'ubuntu-latest' steps: + - checkout: self + submodules: true - task: NodeTool@0 displayName: Use Node.js $(nodeVersion) inputs: @@ -110,7 +113,7 @@ stages: } } - foreach($csproj in Get-ChildItem –Path "src/" -Recurse -Filter *.csproj) + foreach($csproj in Get-ChildItem -Recurse -Filter *.csproj) { dotnet pack $csproj --configuration $(buildConfiguration) --no-build --output $(Build.ArtifactStagingDirectory)/nupkg } diff --git a/src/Umbraco.Cms.Api.Common/Builders/ProblemDetailsBuilder.cs b/src/Umbraco.Cms.Api.Common/Builders/ProblemDetailsBuilder.cs index bc49851911..d3897d5377 100644 --- a/src/Umbraco.Cms.Api.Common/Builders/ProblemDetailsBuilder.cs +++ b/src/Umbraco.Cms.Api.Common/Builders/ProblemDetailsBuilder.cs @@ -7,7 +7,6 @@ public class ProblemDetailsBuilder { private string? _title; private string? _detail; - private int _status = StatusCodes.Status400BadRequest; private string? _type; public ProblemDetailsBuilder WithTitle(string title) @@ -22,12 +21,6 @@ public class ProblemDetailsBuilder return this; } - public ProblemDetailsBuilder WithStatus(int status) - { - _status = status; - return this; - } - public ProblemDetailsBuilder WithType(string type) { _type = type; @@ -39,7 +32,6 @@ public class ProblemDetailsBuilder { Title = _title, Detail = _detail, - Status = _status, Type = _type ?? "Error", }; } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/AuditLogControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/AuditLogControllerBase.cs new file mode 100644 index 0000000000..29293c67b7 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/AuditLogControllerBase.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Routing; + +namespace Umbraco.Cms.Api.Management.Controllers.AuditLog; + +[ApiController] +[VersionedApiBackOfficeRoute("audit-log")] +[ApiExplorerSettings(GroupName = "Audit Log")] +[ApiVersion("1.0")] +public class AuditLogControllerBase : ManagementApiControllerBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByKeyAuditLogController.cs b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByKeyAuditLogController.cs new file mode 100644 index 0000000000..b94139dd8c --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByKeyAuditLogController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.AuditLogs; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.New.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.Controllers.AuditLog; + +public class ByKeyAuditLogController : AuditLogControllerBase +{ + private readonly IAuditService _auditService; + private readonly IAuditLogViewModelFactory _auditLogViewModelFactory; + + public ByKeyAuditLogController(IAuditService auditService, IAuditLogViewModelFactory auditLogViewModelFactory) + { + _auditService = auditService; + _auditLogViewModelFactory = auditLogViewModelFactory; + } + + [HttpGet("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + public async Task ByKey(Guid key, Direction orderDirection = Direction.Descending, DateTime? sinceDate = null, int skip = 0, int take = 100) + { + PagedModel result = await _auditService.GetItemsByKeyAsync(key, skip, take, orderDirection, sinceDate); + IEnumerable mapped = _auditLogViewModelFactory.CreateAuditLogViewModel(result.Items); + var viewModel = new PagedViewModel + { + Total = result.Total, + Items = mapped, + }; + + return Ok(viewModel); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByTypeAuditLogController.cs b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByTypeAuditLogController.cs new file mode 100644 index 0000000000..0fd14ea891 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/ByTypeAuditLogController.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.AuditLogs; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.AuditLog; + +public class ByTypeAuditLogController : AuditLogControllerBase +{ + private readonly IAuditService _auditService; + private readonly IAuditLogViewModelFactory _auditLogViewModelFactory; + + public ByTypeAuditLogController(IAuditService auditService, IAuditLogViewModelFactory auditLogViewModelFactory) + { + _auditService = auditService; + _auditLogViewModelFactory = auditLogViewModelFactory; + } + + [HttpGet("type/{logType}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + public async Task ByType(AuditType logType, DateTime? sinceDate = null, int skip = 0, int take = 100) + { + IAuditItem[] result = _auditService.GetLogs(logType, sinceDate).ToArray(); + IEnumerable mapped = _auditLogViewModelFactory.CreateAuditLogViewModel(result.Skip(skip).Take(take)); + var viewModel = new PagedViewModel + { + Total = result.Length, + Items = mapped, + }; + + return await Task.FromResult(Ok(viewModel)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/CurrentUserAuditLogController.cs b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/CurrentUserAuditLogController.cs new file mode 100644 index 0000000000..d34192756a --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/AuditLog/CurrentUserAuditLogController.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.AuditLogs; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Exceptions; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.New.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.Controllers.AuditLog; + +public class CurrentUserAuditLogController : AuditLogControllerBase +{ + private readonly IAuditService _auditService; + private readonly IAuditLogViewModelFactory _auditLogViewModelFactory; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + private readonly IUserService _userService; + + public CurrentUserAuditLogController( + IAuditService auditService, + IAuditLogViewModelFactory auditLogViewModelFactory, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor, + IUserService userService) + { + _auditService = auditService; + _auditLogViewModelFactory = auditLogViewModelFactory; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + _userService = userService; + } + + [HttpGet] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + public async Task CurrentUser(Direction orderDirection = Direction.Descending, DateTime? sinceDate = null, int skip = 0, int take = 100) + { + // FIXME: Pull out current backoffice user when its implemented. + // var userId = _backOfficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1; + var userId = Constants.Security.SuperUserId; + + IUser? user = _userService.GetUserById(userId); + + if (user is null) + { + throw new PanicException("Could not find current user"); + } + + PagedModel result = await _auditService.GetPagedItemsByUserAsync( + user.Key, + skip, + take, + orderDirection, + null, + sinceDate); + + IEnumerable mapped = _auditLogViewModelFactory.CreateAuditLogWithUsernameViewModels(result.Items.Skip(skip).Take(take)); + var viewModel = new PagedViewModel + { + Total = result.Total, + Items = mapped, + }; + + return Ok(viewModel); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DictionaryControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DictionaryControllerBase.cs index 02e49021d2..5f417acf76 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DictionaryControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/DictionaryControllerBase.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.Builders; using Umbraco.Cms.Api.Management.Routing; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/ByKeyDocumentController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/ByKeyDocumentController.cs new file mode 100644 index 0000000000..1644eac84b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/ByKeyDocumentController.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Document; + +public class ByKeyDocumentController : DocumentControllerBase +{ + private readonly IContentService _contentService; + private readonly IDocumentViewModelFactory _documentViewModelFactory; + + public ByKeyDocumentController(IContentService contentService, IDocumentViewModelFactory documentViewModelFactory) + { + _contentService = contentService; + _documentViewModelFactory = documentViewModelFactory; + } + + [HttpGet("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(DocumentViewModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task ByKey(Guid key) + { + // FIXME: create and use an async get method here. + IContent? content = _contentService.GetById(key); + if (content == null) + { + return NotFound(); + } + + DocumentViewModel model = await _documentViewModelFactory.CreateViewModelAsync(content); + return Ok(model); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/DocumentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/DocumentControllerBase.cs new file mode 100644 index 0000000000..fe8bd8a1c0 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/DocumentControllerBase.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core; + +namespace Umbraco.Cms.Api.Management.Controllers.Document; + +[ApiVersion("1.0")] +[ApiController] +[VersionedApiBackOfficeRoute(Constants.UdiEntityType.Document)] +[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Document))] +public abstract class DocumentControllerBase : ManagementApiControllerBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ByKeyDocumentTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ByKeyDocumentTypeController.cs new file mode 100644 index 0000000000..8fc1d43293 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/ByKeyDocumentTypeController.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.DocumentType; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentType; + +public class ByKeyDocumentTypeController : DocumentTypeControllerBase +{ + private readonly IContentTypeService _contentTypeService; + private readonly IUmbracoMapper _umbracoMapper; + + public ByKeyDocumentTypeController(IContentTypeService contentTypeService, IUmbracoMapper umbracoMapper) + { + _contentTypeService = contentTypeService; + _umbracoMapper = umbracoMapper; + } + + [HttpGet("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(DocumentTypeViewModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task ByKey(Guid key) + { + // FIXME: create and use an async get method here. + IContentType? contentType = _contentTypeService.Get(key); + if (contentType == null) + { + return NotFound(); + } + + DocumentTypeViewModel model = _umbracoMapper.Map(contentType)!; + return await Task.FromResult(Ok(model)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentControllerBase.cs new file mode 100644 index 0000000000..d5279cfde4 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/DocumentControllerBase.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core; + +namespace Umbraco.Cms.Api.Management.Controllers.DocumentType; + +[ApiVersion("1.0")] +[ApiController] +[VersionedApiBackOfficeRoute(Constants.UdiEntityType.DocumentType)] +[ApiExplorerSettings(GroupName = "Document Type")] +public abstract class DocumentTypeControllerBase : ManagementApiControllerBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/LogLevelCountLogViewerController.cs b/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/LogLevelCountLogViewerController.cs index 145bcd1eb8..942deffbbf 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/LogLevelCountLogViewerController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/LogViewer/LogLevelCountLogViewerController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; using Umbraco.Cms.Api.Management.ViewModels.LogViewer; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Logging.Viewer; @@ -29,7 +30,7 @@ public class LogLevelCountLogViewerController : LogViewerControllerBase [HttpGet("level-count")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(LogLevelCountsViewModel), StatusCodes.Status200OK)] public async Task LogLevelCounts(DateTime? startDate = null, DateTime? endDate = null) { Attempt logLevelCountsAttempt = diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/AllMigrationStatusPackageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/AllMigrationStatusPackageController.cs new file mode 100644 index 0000000000..afb7ffda69 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/AllMigrationStatusPackageController.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.ViewModels.Package; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Services; +using Umbraco.New.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.Controllers.Package; + +public class AllMigrationStatusPackageController : PackageControllerBase +{ + private readonly IPackagingService _packagingService; + private readonly IUmbracoMapper _umbracoMapper; + + public AllMigrationStatusPackageController(IPackagingService packagingService, IUmbracoMapper umbracoMapper) + { + _packagingService = packagingService; + _umbracoMapper = umbracoMapper; + } + + /// + /// Gets a paginated list of the migration status of each installed package. + /// + /// The amount of items to skip. + /// The amount of items to take. + /// The paged result of the installed packages migration status. + [HttpGet("migration-status")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + public async Task>> AllMigrationStatuses(int skip = 0, int take = 100) + { + PagedModel migrationPlans = await _packagingService.GetInstalledPackagesFromMigrationPlansAsync(skip, take); + + IEnumerable viewModels = _umbracoMapper.MapEnumerable(migrationPlans.Items); + + return Ok(new PagedViewModel() + { + Total = migrationPlans.Total, + Items = viewModels, + }); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/AllCreatedPackageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/AllCreatedPackageController.cs new file mode 100644 index 0000000000..2500071124 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/AllCreatedPackageController.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.ViewModels.Package; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Controllers.Package.Created; + +public class AllCreatedPackageController : CreatedPackageControllerBase +{ + private readonly IPackagingService _packagingService; + private readonly IUmbracoMapper _umbracoMapper; + + public AllCreatedPackageController(IPackagingService packagingService, IUmbracoMapper umbracoMapper) + { + _packagingService = packagingService; + _umbracoMapper = umbracoMapper; + } + + /// + /// Gets a paginated list of all created packages. + /// + /// The amount of items to skip. + /// The amount of items to take. + /// The paged result of the created packages. + [HttpGet] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + public async Task>> All(int skip = 0, int take = 100) + { + IEnumerable createdPackages = _packagingService + .GetAllCreatedPackages() + .WhereNotNull() + .Skip(skip) + .Take(take); + + return await Task.FromResult(Ok(_umbracoMapper.Map>(createdPackages))); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/ByKeyCreatedPackageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/ByKeyCreatedPackageController.cs new file mode 100644 index 0000000000..5d53ef3548 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/ByKeyCreatedPackageController.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Package; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Package.Created; + +public class ByKeyCreatedPackageController : CreatedPackageControllerBase +{ + private readonly IPackagingService _packagingService; + private readonly IUmbracoMapper _umbracoMapper; + + public ByKeyCreatedPackageController(IPackagingService packagingService, IUmbracoMapper umbracoMapper) + { + _packagingService = packagingService; + _umbracoMapper = umbracoMapper; + } + + /// + /// Gets a package by key. + /// + /// The key of the package. + /// The package or not found result. + [HttpGet("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(PackageDefinitionViewModel), StatusCodes.Status200OK)] + public async Task> ByKey(Guid key) + { + PackageDefinition? package = await _packagingService.GetCreatedPackageByKeyAsync(key); + + if (package is null) + { + return NotFound(); + } + + return Ok(_umbracoMapper.Map(package)); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/CreateCreatedPackageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/CreateCreatedPackageController.cs new file mode 100644 index 0000000000..9edeb024a9 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/CreateCreatedPackageController.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.Package; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.Package.Created; + +public class CreateCreatedPackageController : CreatedPackageControllerBase +{ + private readonly IPackagingService _packagingService; + private readonly IPackageDefinitionFactory _packageDefinitionFactory; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public CreateCreatedPackageController( + IPackagingService packagingService, + IPackageDefinitionFactory packageDefinitionFactory, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _packagingService = packagingService; + _packageDefinitionFactory = packageDefinitionFactory; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } + + /// + /// Creates a package. + /// + /// The model containing the data for a new package. + /// The created package. + [HttpPost] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task Create(PackageCreateModel packageCreateModel) + { + PackageDefinition packageDefinition = _packageDefinitionFactory.CreatePackageDefinition(packageCreateModel); + + Attempt result = await _packagingService.CreateCreatedPackageAsync(packageDefinition, CurrentUserId(_backOfficeSecurityAccessor)); + + return result.Success + ? CreatedAtAction(controller => nameof(controller.ByKey), packageDefinition.PackageId) + : PackageOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/CreatedPackageControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/CreatedPackageControllerBase.cs new file mode 100644 index 0000000000..a1d9bd9cd9 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/CreatedPackageControllerBase.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Routing; + +namespace Umbraco.Cms.Api.Management.Controllers.Package.Created; + +[ApiController] +[VersionedApiBackOfficeRoute("package/created")] +[ApiExplorerSettings(GroupName = "Package")] +[ApiVersion("1.0")] +public class CreatedPackageControllerBase : PackageControllerBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/DeleteCreatedPackageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/DeleteCreatedPackageController.cs new file mode 100644 index 0000000000..cbc71b7d20 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/DeleteCreatedPackageController.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.Package.Created; + +public class DeleteCreatedPackageController : CreatedPackageControllerBase +{ + private readonly IPackagingService _packagingService; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public DeleteCreatedPackageController(IPackagingService packagingService, IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _packagingService = packagingService; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } + + /// + /// Deletes a package with a given key. + /// + /// The key of the package. + /// The result of the deletion. + [HttpDelete("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task Delete(Guid key) + { + Attempt result = + await _packagingService.DeleteCreatedPackageAsync(key, CurrentUserId(_backOfficeSecurityAccessor)); + + return result.Success + ? Ok() + : PackageOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/DownloadCreatedPackageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/DownloadCreatedPackageController.cs new file mode 100644 index 0000000000..1c13d0596b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/DownloadCreatedPackageController.cs @@ -0,0 +1,59 @@ +using System.Net; +using System.Net.Mime; +using System.Text; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; +using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Package.Created; + +public class DownloadCreatedPackageController : CreatedPackageControllerBase +{ + private readonly IPackagingService _packagingService; + + public DownloadCreatedPackageController(IPackagingService packagingService) => _packagingService = packagingService; + + /// + /// Downloads a package XML or ZIP file. + /// + /// The key of the package. + /// The XML or ZIP file of the package or not found result. + [HttpGet("{key:guid}/download")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] + public async Task Download(Guid key) + { + PackageDefinition? package = await _packagingService.GetCreatedPackageByKeyAsync(key); + + if (package is null) + { + return NotFound(); + } + + Stream? fileStream = _packagingService.GetPackageFileStream(package); + if (fileStream is null) + { + return NotFound(); + } + + var fileName = Path.GetFileName(package.PackagePath); + Encoding encoding = Encoding.UTF8; + + var contentDisposition = new ContentDisposition + { + FileName = WebUtility.UrlEncode(fileName), + DispositionType = DispositionTypeNames.Attachment + }; + + Response.Headers.Add("Content-Disposition", contentDisposition.ToString()); + + var result = new FileStreamResult( + fileStream, + new MediaTypeHeaderValue(MediaTypeNames.Application.Octet) { Charset = encoding.WebName }); + + return result; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/UpdateCreatedPackageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/UpdateCreatedPackageController.cs new file mode 100644 index 0000000000..895d889193 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/Created/UpdateCreatedPackageController.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Package; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Packaging; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.Package.Created; + +public class UpdateCreatedPackageController : CreatedPackageControllerBase +{ + private readonly IPackagingService _packagingService; + private readonly IUmbracoMapper _umbracoMapper; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public UpdateCreatedPackageController( + IPackagingService packagingService, + IUmbracoMapper umbracoMapper, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _packagingService = packagingService; + _umbracoMapper = umbracoMapper; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } + + /// + /// Updates a package. + /// + /// The key of the package. + /// The model containing the data for updating a package. + /// The created package. + [HttpPut("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task Update(Guid key, PackageUpdateModel packageUpdateModel) + { + PackageDefinition? package = await _packagingService.GetCreatedPackageByKeyAsync(key); + + if (package is null) + { + return NotFound(); + } + + // Macros are not included! + PackageDefinition packageDefinition = _umbracoMapper.Map(packageUpdateModel, package); + + Attempt result = await _packagingService.UpdateCreatedPackageAsync(packageDefinition, CurrentUserId(_backOfficeSecurityAccessor)); + + return result.Success + ? Ok() + : PackageOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/PackageControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/PackageControllerBase.cs index 8e7684b439..0ec683d7b3 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Package/PackageControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/PackageControllerBase.cs @@ -1,5 +1,8 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.Builders; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Package; @@ -9,4 +12,29 @@ namespace Umbraco.Cms.Api.Management.Controllers.Package; [ApiVersion("1.0")] public abstract class PackageControllerBase : ManagementApiControllerBase { + protected IActionResult PackageOperationStatusResult(PackageOperationStatus status) => + status switch + { + PackageOperationStatus.NotFound => NotFound("The package could not be found"), + PackageOperationStatus.DuplicateItemName => Conflict(new ProblemDetailsBuilder() + .WithTitle("Duplicate package name") + .WithDetail("Another package already exists with the attempted name.") + .Build()), + PackageOperationStatus.InvalidName => BadRequest(new ProblemDetailsBuilder() + .WithTitle("Invalid package name") + .WithDetail("The attempted package name does not represent a valid name for a package.") + .Build()), + _ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown package operation status") + }; + + protected IActionResult PackageMigrationOperationStatusResult(PackageMigrationOperationStatus status) => + status switch + { + PackageMigrationOperationStatus.NotFound => NotFound("No migration plans were found for that package"), + PackageMigrationOperationStatus.CancelledByFailedMigration => Conflict(new ProblemDetailsBuilder() + .WithTitle("Package migration failed") + .WithDetail("Check log for full details about the failed migration.") + .Build()), + _ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown package migration operation status") + }; } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Package/RunMigrationPackageController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Package/RunMigrationPackageController.cs new file mode 100644 index 0000000000..1c3fc6fd31 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Package/RunMigrationPackageController.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Infrastructure.Install; + +namespace Umbraco.Cms.Api.Management.Controllers.Package; + +public class RunMigrationPackageController : PackageControllerBase +{ + private readonly PackageMigrationRunner _packageMigrationRunner; + + public RunMigrationPackageController(PackageMigrationRunner packageMigrationRunner) + => _packageMigrationRunner = packageMigrationRunner; + + /// + /// Runs all migration plans for a package with a given name if any are pending. + /// + /// The name of the package. + /// The result of running the package migrations. + [HttpPost("{name}/run-migration")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task RunMigrations(string name) + { + Attempt result = await _packageMigrationRunner.RunPendingPackageMigrations(name); + + return result.Success + ? Ok() + : PackageMigrationOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Profiling/GetStatusProfilingController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Profiling/GetStatusProfilingController.cs new file mode 100644 index 0000000000..700689f0a3 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Profiling/GetStatusProfilingController.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Profiling; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Profiling; + +public class GetStatusProfilingController : ProfilingControllerBase +{ + private readonly IWebProfilerService _webProfilerService; + + public GetStatusProfilingController(IWebProfilerService webProfilerService) => _webProfilerService = webProfilerService; + + [HttpGet("status")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(ProfilingStatusViewModel), StatusCodes.Status200OK)] + public async Task Status() + { + var result = await _webProfilerService.GetStatus(); + return result.Success + ? Ok(new ProfilingStatusViewModel(result.Result)) + : WebProfilerOperationStatusResult(result.Status); + } +} + diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Profiling/ProfilingControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Profiling/ProfilingControllerBase.cs index 07b068f5c9..49be02c341 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Profiling/ProfilingControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Profiling/ProfilingControllerBase.cs @@ -1,5 +1,8 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.Builders; using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core.Services.OperationStatus; namespace Umbraco.Cms.Api.Management.Controllers.Profiling; @@ -9,4 +12,15 @@ namespace Umbraco.Cms.Api.Management.Controllers.Profiling; [ApiExplorerSettings(GroupName = "Profiling")] public class ProfilingControllerBase : ManagementApiControllerBase { + + protected IActionResult WebProfilerOperationStatusResult(WebProfilerOperationStatus status) => + status switch + { + WebProfilerOperationStatus.ExecutingUserNotFound => Unauthorized(new ProblemDetailsBuilder() + .WithTitle("Executing user not found") + .WithDetail("Executing this action requires a signed in user.") + .Build()), + + _ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown profiling operation status") + }; } diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Profiling/StatusProfilingController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Profiling/StatusProfilingController.cs deleted file mode 100644 index 5738655e14..0000000000 --- a/src/Umbraco.Cms.Api.Management/Controllers/Profiling/StatusProfilingController.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Cms.Core.Hosting; -using Umbraco.Cms.Api.Management.ViewModels.Profiling; - -namespace Umbraco.Cms.Api.Management.Controllers.Profiling; - -public class StatusProfilingController : ProfilingControllerBase -{ - private readonly IHostingEnvironment _hosting; - - public StatusProfilingController(IHostingEnvironment hosting) => _hosting = hosting; - - [HttpGet("status")] - [MapToApiVersion("1.0")] - [ProducesResponseType(typeof(ProfilingStatusViewModel), StatusCodes.Status200OK)] - public async Task> Status() - => await Task.FromResult(Ok(new ProfilingStatusViewModel(_hosting.IsDebugMode))); -} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Profiling/UpdateStatusProfilingController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Profiling/UpdateStatusProfilingController.cs new file mode 100644 index 0000000000..15ed5bae45 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/Profiling/UpdateStatusProfilingController.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.ViewModels.Profiling; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.Profiling; + +public class UpdateStatusProfilingController : ProfilingControllerBase +{ + private readonly IWebProfilerService _webProfilerService; + + public UpdateStatusProfilingController(IWebProfilerService webProfilerService) => _webProfilerService = webProfilerService; + + [HttpPut("status")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task Status(ProfilingStatusViewModel model) + { + var result = await _webProfilerService.SetStatus(model.Enabled); + return result.Success + ? Ok() + : WebProfilerOperationStatusResult(result.Status); + } +} + diff --git a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ByIdTrackedReferenceController.cs b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ByIdTrackedReferenceController.cs index 39d2cfe3c2..57b24dd4b4 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ByIdTrackedReferenceController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ByIdTrackedReferenceController.cs @@ -27,16 +27,16 @@ public class ByIdTrackedReferenceController : TrackedReferenceControllerBase /// Used by info tabs on content, media etc. and for the delete and unpublish of single items. /// This is basically finding parents of relations. /// - [HttpGet("{id:int}")] + [HttpGet("{key:guid}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] public async Task>> Get( - int id, - long skip, - long take, - bool? filterMustBeIsDependency) + Guid key, + long skip = 0, + long take = 20, + bool filterMustBeIsDependency = false) { - PagedModel relationItems = _trackedReferencesService.GetPagedRelationsForItem(id, skip, take, filterMustBeIsDependency ?? false); + PagedModel relationItems = await _trackedReferencesService.GetPagedRelationsForItemAsync(key, skip, take, filterMustBeIsDependency); var pagedViewModel = new PagedViewModel { diff --git a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/DescendantsTrackedReferenceController.cs b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/DescendantsTrackedReferenceController.cs index 752b6d8846..899b8ff500 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/DescendantsTrackedReferenceController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/DescendantsTrackedReferenceController.cs @@ -28,12 +28,12 @@ public class DescendantsTrackedReferenceController : TrackedReferenceControllerB /// kind of relation. /// This is basically finding the descending items which are children in relations. /// - [HttpGet("descendants/{parentId:int}")] + [HttpGet("descendants/{parentKey:guid}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task>> Descendants(int parentId, long skip, long take, bool? filterMustBeIsDependency) + public async Task>> Descendants(Guid parentKey, long skip, long take, bool filterMustBeIsDependency = true) { - PagedModel relationItems = _trackedReferencesSkipTakeService.GetPagedDescendantsInReferences(parentId, skip, take, filterMustBeIsDependency ?? true); + PagedModel relationItems = await _trackedReferencesSkipTakeService.GetPagedDescendantsInReferencesAsync(parentKey, skip, take, filterMustBeIsDependency); var pagedViewModel = new PagedViewModel { Total = relationItems.Total, diff --git a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ItemsTrackedReferenceController.cs b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ItemsTrackedReferenceController.cs index ecc0e3434d..59e3ba0503 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ItemsTrackedReferenceController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/TrackedReference/ItemsTrackedReferenceController.cs @@ -21,7 +21,7 @@ public class ItemsTrackedReferenceController : TrackedReferenceControllerBase } /// - /// Gets a page list of the items used in any kind of relation from selected integer ids. + /// Gets a page list of the items used in any kind of relation from selected keys. /// /// /// Used when bulk deleting content/media and bulk unpublishing content (delete and unpublish on List view). @@ -30,9 +30,9 @@ public class ItemsTrackedReferenceController : TrackedReferenceControllerBase [HttpGet("item")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] - public async Task>> GetPagedReferencedItems([FromQuery]int[] ids, long skip, long take, bool? filterMustBeIsDependency) + public async Task>> GetPagedReferencedItems([FromQuery(Name="key")]SortedSet keys, long skip = 0, long take = 20, bool filterMustBeIsDependency = true) { - PagedModel relationItems = _trackedReferencesSkipTakeService.GetPagedItemsWithRelations(ids, skip, take, filterMustBeIsDependency ?? true); + PagedModel relationItems = await _trackedReferencesSkipTakeService.GetPagedItemsWithRelationsAsync(keys, skip, take, filterMustBeIsDependency); var pagedViewModel = new PagedViewModel { Total = relationItems.Total, diff --git a/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/ByKeyUserGroupController.cs b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/ByKeyUserGroupController.cs new file mode 100644 index 0000000000..c335926e74 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/ByKeyUserGroupController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.UserGroups; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Controllers.UserGroups; + + +public class ByKeyUserGroupController : UserGroupsControllerBase +{ + private readonly IUserGroupService _userGroupService; + private readonly IUserGroupViewModelFactory _userGroupViewModelFactory; + + public ByKeyUserGroupController( + IUserGroupService userGroupService, + IUserGroupViewModelFactory userGroupViewModelFactory) + { + _userGroupService = userGroupService; + _userGroupViewModelFactory = userGroupViewModelFactory; + } + + [HttpGet("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(UserGroupViewModel), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> ByKey(Guid key) + { + IUserGroup? userGroup = await _userGroupService.GetAsync(key); + + if (userGroup is null) + { + return NotFound(); + } + + return await _userGroupViewModelFactory.CreateAsync(userGroup); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/CreateUserGroupController.cs b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/CreateUserGroupController.cs new file mode 100644 index 0000000000..755cb1dd4b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/CreateUserGroupController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.UserGroups; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.UserGroups; + +public class CreateUserGroupController : UserGroupsControllerBase +{ + private readonly IUserGroupService _userGroupService; + private readonly IUserGroupViewModelFactory _userGroupViewModelFactory; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public CreateUserGroupController( + IUserGroupService userGroupService, + IUserGroupViewModelFactory userGroupViewModelFactory, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _userGroupService = userGroupService; + _userGroupViewModelFactory = userGroupViewModelFactory; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } + + [HttpPost] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status201Created)] + public async Task Create(UserGroupSaveModel userGroupSaveModel) + { + // FIXME: Comment this in when auth is in place and we can get a currently logged in user. + // IUser? currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; + // if (currentUser is null) + // { + // return UserGroupOperationStatusResult(UserGroupOperationStatus.MissingUser); + // } + + Attempt userGroupCreationAttempt = await _userGroupViewModelFactory.CreateAsync(userGroupSaveModel); + if (userGroupCreationAttempt.Success is false) + { + return UserGroupOperationStatusResult(userGroupCreationAttempt.Status); + } + + IUserGroup group = userGroupCreationAttempt.Result; + + Attempt result = await _userGroupService.CreateAsync(group, /*currentUser.Id*/ -1); + return result.Success + ? CreatedAtAction(controller => nameof(controller.ByKey), group.Key) + : UserGroupOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/DeleteUserGroupController.cs b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/DeleteUserGroupController.cs new file mode 100644 index 0000000000..ad26e28cc2 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/DeleteUserGroupController.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.UserGroups; + +public class DeleteUserGroupController : UserGroupsControllerBase +{ + private readonly IUserGroupService _userGroupService; + + public DeleteUserGroupController(IUserGroupService userGroupService) + { + _userGroupService = userGroupService; + } + + [HttpDelete("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task Delete(Guid key) + { + Attempt result = await _userGroupService.DeleteAsync(key); + + return result.Success + ? Ok() + : UserGroupOperationStatusResult(result.Result); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/GetAllUserGroupController.cs b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/GetAllUserGroupController.cs new file mode 100644 index 0000000000..86b61019fc --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/GetAllUserGroupController.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.UserGroups; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Services; +using Umbraco.New.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.Controllers.UserGroups; + +public class GetAllUserGroupController : UserGroupsControllerBase +{ + private readonly IUserGroupService _userGroupService; + private readonly IUserGroupViewModelFactory _userViewModelFactory; + + public GetAllUserGroupController( + IUserGroupService userGroupService, + IUserGroupViewModelFactory userViewModelFactory) + { + _userGroupService = userGroupService; + _userViewModelFactory = userViewModelFactory; + } + + [HttpGet] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(PagedViewModel), StatusCodes.Status200OK)] + public async Task>> GetAll(int skip = 0, int take = 100) + { + // FIXME: In the old controller this endpoint had a switch "onlyCurrentUserGroup" + // If this was enabled we'd only return the groups the current user was in + // and even if it was set to false we'd still remove the admin group. + // We still need to have this functionality, however, it does not belong here. + // Instead we should implement this functionality on the CurrentUserController + PagedModel userGroups = await _userGroupService.GetAllAsync(skip, take); + + var viewModels = (await _userViewModelFactory.CreateMultipleAsync(userGroups.Items)).ToList(); + return new PagedViewModel { Total = userGroups.Total, Items = viewModels }; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/UpdateUserGroupController.cs b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/UpdateUserGroupController.cs new file mode 100644 index 0000000000..dc3f5bac70 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/UpdateUserGroupController.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.ViewModels.UserGroups; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.UserGroups; + +public class UpdateUserGroupController : UserGroupsControllerBase +{ + private readonly IUserGroupService _userGroupService; + private readonly IUserGroupViewModelFactory _userGroupViewModelFactory; + + public UpdateUserGroupController( + IUserGroupService userGroupService, + IUserGroupViewModelFactory userGroupViewModelFactory) + { + _userGroupService = userGroupService; + _userGroupViewModelFactory = userGroupViewModelFactory; + } + + [HttpPut("{key:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task Update(Guid key, UserGroupUpdateModel dataTypeUpdateModel) + { + IUserGroup? existingUserGroup = await _userGroupService.GetAsync(key); + + if (existingUserGroup is null) + { + return UserGroupOperationStatusResult(UserGroupOperationStatus.NotFound); + } + + Attempt userGroupUpdateAttempt = await _userGroupViewModelFactory.UpdateAsync(existingUserGroup, dataTypeUpdateModel); + if (userGroupUpdateAttempt.Success is false) + { + return UserGroupOperationStatusResult(userGroupUpdateAttempt.Status); + } + + IUserGroup userGroup = userGroupUpdateAttempt.Result; + Attempt result = await _userGroupService.UpdateAsync(userGroup, -1); + + return result.Success + ? Ok() + : UserGroupOperationStatusResult(result.Status); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/UserGroupsControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/UserGroupsControllerBase.cs new file mode 100644 index 0000000000..a24edfd973 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Controllers/UserGroups/UserGroupsControllerBase.cs @@ -0,0 +1,83 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.Builders; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Controllers.UserGroups; + +// TODO: This needs to be an authorized controller. + +[ApiController] +[VersionedApiBackOfficeRoute("user-groups")] +[ApiExplorerSettings(GroupName = "User Groups")] +[ApiVersion("1.0")] +public class UserGroupsControllerBase : ManagementApiControllerBase +{ + protected IActionResult UserGroupOperationStatusResult(UserGroupOperationStatus status) => + status switch + { + UserGroupOperationStatus.NotFound => NotFound("The user group could not be found"), + UserGroupOperationStatus.AlreadyExists => Conflict(new ProblemDetailsBuilder() + .WithTitle("User group already exists") + .WithDetail("The user group exists already.") + .Build()), + UserGroupOperationStatus.DuplicateAlias => Conflict(new ProblemDetailsBuilder() + .WithTitle("Duplicate alias") + .WithDetail("A user group already exists with the attempted alias.") + .Build()), + UserGroupOperationStatus.MissingUser => Unauthorized(new ProblemDetailsBuilder() + .WithTitle("Missing user") + .WithDetail("A performing user was not found when attempting to create the user group.") + .Build()), + UserGroupOperationStatus.IsSystemUserGroup => BadRequest(new ProblemDetailsBuilder() + .WithTitle("System user group") + .WithDetail("The operation is not allowed on a system user group.") + .Build()), + UserGroupOperationStatus.UnauthorizedMissingUserSection => Unauthorized(new ProblemDetailsBuilder() + .WithTitle("Unauthorized") + .WithDetail("The performing user does not have access to the required section") + .Build()), + UserGroupOperationStatus.UnauthorizedMissingSections => Unauthorized(new ProblemDetailsBuilder() + .WithTitle("Unauthorized section") + .WithDetail("The specified allowed section contained a section the performing user doesn't have access to.") + .Build()), + UserGroupOperationStatus.UnauthorizedStartNodes => Unauthorized(new ProblemDetailsBuilder() + .WithTitle("Unauthorized start node") + .WithDetail("The specified start nodes contained a start node the performing user doesn't have access to.") + .Build()), + UserGroupOperationStatus.UnauthorizedMissingUserGroup => Unauthorized(new ProblemDetailsBuilder() + .WithTitle("User not in user group") + .WithDetail("The current user is not in the user group") + .Build()), + UserGroupOperationStatus.CancelledByNotification => BadRequest(new ProblemDetailsBuilder() + .WithTitle("Cancelled by notification") + .WithDetail("A notification handler prevented the language operation.") + .Build()), + UserGroupOperationStatus.DocumentStartNodeKeyNotFound => NotFound(new ProblemDetailsBuilder() + .WithTitle("Document start node key not found") + .WithDetail("The assigned document start node does not exists.") + .Build()), + UserGroupOperationStatus.MediaStartNodeKeyNotFound => NotFound(new ProblemDetailsBuilder() + .WithTitle("Media start node key not found") + .WithDetail("The assigned media start node does not exists.") + .Build()), + UserGroupOperationStatus.LanguageNotFound => NotFound(new ProblemDetailsBuilder() + .WithTitle("Language not found") + .WithDetail("The specified language cannot be found.") + .Build()), + UserGroupOperationStatus.NameTooLong => BadRequest(new ProblemDetailsBuilder() + .WithTitle("Name too long") + .WithDetail("User Group name is too long.") + .Build()), + UserGroupOperationStatus.AliasTooLong => BadRequest(new ProblemDetailsBuilder() + .WithTitle("Alias too long") + .WithDetail("The user group alias is too long.") + .Build()), + UserGroupOperationStatus.MissingName => BadRequest(new ProblemDetailsBuilder() + .WithTitle("Missing user group name.") + .WithDetail("The user group name is required, and cannot be an empty string.") + .Build()), + _ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown user group operation status."), + }; +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/AuditLogBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/AuditLogBuilderExtensions.cs new file mode 100644 index 0000000000..392e9ecf57 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/AuditLogBuilderExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Core.DependencyInjection; + +namespace Umbraco.Cms.Api.Management.DependencyInjection; + +internal static class AuditLogBuilderExtensions +{ + internal static IUmbracoBuilder AddAuditLogs(this IUmbracoBuilder builder) + { + builder.Services.AddTransient(); + + return builder; + } +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/DocumentBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/DocumentBuilderExtensions.cs new file mode 100644 index 0000000000..b3c6130614 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/DocumentBuilderExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Mapping.Document; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Mapping; + +namespace Umbraco.Cms.Api.Management.DependencyInjection; + +internal static class DocumentBuilderExtensions +{ + internal static IUmbracoBuilder AddDocuments(this IUmbracoBuilder builder) + { + builder.Services.AddTransient(); + builder.Services.AddTransient(); + + builder.WithCollectionBuilder().Add(); + + return builder; + } +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/DocumentTypeBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/DocumentTypeBuilderExtensions.cs new file mode 100644 index 0000000000..f0bbb227c1 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/DocumentTypeBuilderExtensions.cs @@ -0,0 +1,15 @@ +using Umbraco.Cms.Api.Management.Mapping.DocumentType; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Mapping; + +namespace Umbraco.Cms.Api.Management.DependencyInjection; + +internal static class DocumentTypeBuilderExtensions +{ + internal static IUmbracoBuilder AddDocumentTypes(this IUmbracoBuilder builder) + { + builder.WithCollectionBuilder().Add(); + + return builder; + } +} diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/PackageBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/PackageBuilderExtensions.cs index 24e4c9acc9..3fad1574f4 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/PackageBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/PackageBuilderExtensions.cs @@ -1,4 +1,6 @@ -using Umbraco.Cms.Api.Management.Mapping.Package; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Api.Management.Mapping.Package; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Mapping; @@ -8,7 +10,9 @@ internal static class PackageBuilderExtensions { internal static IUmbracoBuilder AddPackages(this IUmbracoBuilder builder) { - builder.WithCollectionBuilder().Add(); + builder.Services.AddTransient(); + + builder.WithCollectionBuilder().Add(); return builder; } diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/UserGroupsBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/UserGroupsBuilderExtensions.cs new file mode 100644 index 0000000000..0cb4cf7595 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/UserGroupsBuilderExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Api.Management.Factories; +using Umbraco.Cms.Core.DependencyInjection; + +namespace Umbraco.Cms.Api.Management.DependencyInjection; + +internal static class UserGroupsBuilderExtensions +{ + internal static IUmbracoBuilder AddUserGroups(this IUmbracoBuilder builder) + { + builder.Services.AddTransient(); + return builder; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/AuditLogViewModelFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/AuditLogViewModelFactory.cs new file mode 100644 index 0000000000..6cbaa5a794 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/AuditLogViewModelFactory.cs @@ -0,0 +1,81 @@ +using Umbraco.Cms.Api.Management.ViewModels.AuditLogs; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Media; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Factories; + +public class AuditLogViewModelFactory : IAuditLogViewModelFactory +{ + private readonly IUserService _userService; + private readonly AppCaches _appCaches; + private readonly MediaFileManager _mediaFileManager; + private readonly IImageUrlGenerator _imageUrlGenerator; + private readonly IEntityService _entityService; + + public AuditLogViewModelFactory(IUserService userService, AppCaches appCaches, MediaFileManager mediaFileManager, IImageUrlGenerator imageUrlGenerator, IEntityService entityService) + { + _userService = userService; + _appCaches = appCaches; + _mediaFileManager = mediaFileManager; + _imageUrlGenerator = imageUrlGenerator; + _entityService = entityService; + } + + public IEnumerable CreateAuditLogViewModel(IEnumerable auditItems) => auditItems.Select(CreateAuditLogViewModel); + + public IEnumerable CreateAuditLogWithUsernameViewModels(IEnumerable auditItems) => auditItems.Select(CreateAuditLogWithUsernameViewModel); + + private AuditLogWithUsernameResponseModel CreateAuditLogWithUsernameViewModel(IAuditItem auditItem) + { + IEntitySlim? entitySlim = _entityService.Get(auditItem.Id); + + var target = new AuditLogWithUsernameResponseModel + { + Comment = auditItem.Comment, + EntityType = auditItem.EntityType, + EntityKey = entitySlim?.Key, + LogType = auditItem.AuditType, + Parameters = auditItem.Parameters, + Timestamp = auditItem.CreateDate, + }; + + IUser? user = _userService.GetUserById(auditItem.UserId); + if (user is null) + { + throw new ArgumentException($"Could not find user with id {auditItem.UserId}"); + } + + target.UserKey = user.Key; + target.UserAvatars = user.GetUserAvatarUrls(_appCaches.RuntimeCache, _mediaFileManager, _imageUrlGenerator); + target.UserName = user.Name; + return target; + } + + private AuditLogResponseModel CreateAuditLogViewModel(IAuditItem auditItem) + { + IEntitySlim? entitySlim = _entityService.Get(auditItem.Id); + var target = new AuditLogResponseModel + { + Comment = auditItem.Comment, + EntityType = auditItem.EntityType, + EntityKey = entitySlim?.Key, + LogType = auditItem.AuditType, + Parameters = auditItem.Parameters, + Timestamp = auditItem.CreateDate, + }; + + IUser? user = _userService.GetUserById(auditItem.UserId); + if (user is null) + { + throw new ArgumentException($"Could not find user with id {auditItem.UserId}"); + } + + target.UserKey = user.Key; + return target; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/ContentUrlFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/ContentUrlFactory.cs new file mode 100644 index 0000000000..9327038b76 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/ContentUrlFactory.cs @@ -0,0 +1,66 @@ +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Api.Management.ViewModels.Content; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Factories; + +public class ContentUrlFactory : IContentUrlFactory +{ + private readonly IPublishedRouter _publishedRouter; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly ILanguageService _languageService; + private readonly ILocalizedTextService _localizedTextService; + private readonly IContentService _contentService; + private readonly IVariationContextAccessor _variationContextAccessor; + private readonly ILoggerFactory _loggerFactory; + private readonly UriUtility _uriUtility; + private readonly IPublishedUrlProvider _publishedUrlProvider; + + public ContentUrlFactory( + IPublishedRouter publishedRouter, + IUmbracoContextAccessor umbracoContextAccessor, + ILanguageService languageService, + ILocalizedTextService localizedTextService, + IContentService contentService, + IVariationContextAccessor variationContextAccessor, + ILoggerFactory loggerFactory, + UriUtility uriUtility, + IPublishedUrlProvider publishedUrlProvider) + { + _publishedRouter = publishedRouter; + _umbracoContextAccessor = umbracoContextAccessor; + _languageService = languageService; + _localizedTextService = localizedTextService; + _contentService = contentService; + _variationContextAccessor = variationContextAccessor; + _loggerFactory = loggerFactory; + _uriUtility = uriUtility; + _publishedUrlProvider = publishedUrlProvider; + } + + public async Task> GetUrlsAsync(IContent content) + { + IUmbracoContext umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + + UrlInfo[] urlInfos = (await content.GetContentUrlsAsync( + _publishedRouter, + umbracoContext, + _languageService, + _localizedTextService, + _contentService, + _variationContextAccessor, + _loggerFactory.CreateLogger(), + _uriUtility, + _publishedUrlProvider)).ToArray(); + + return urlInfos + .Where(urlInfo => urlInfo.IsUrl) + .Select(urlInfo => new ContentUrlInfo { Culture = urlInfo.Culture, Url = urlInfo.Text }) + .ToArray(); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/DocumentViewModelFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DocumentViewModelFactory.cs new file mode 100644 index 0000000000..93f2e553a7 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/DocumentViewModelFactory.cs @@ -0,0 +1,36 @@ +using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Api.Management.Factories; + +public class DocumentViewModelFactory : IDocumentViewModelFactory +{ + private readonly IUmbracoMapper _umbracoMapper; + private readonly IContentUrlFactory _contentUrlFactory; + private readonly IFileService _fileService; + + public DocumentViewModelFactory( + IUmbracoMapper umbracoMapper, + IContentUrlFactory contentUrlFactory, + IFileService fileService) + { + _umbracoMapper = umbracoMapper; + _contentUrlFactory = contentUrlFactory; + _fileService = fileService; + } + + public async Task CreateViewModelAsync(IContent content) + { + DocumentViewModel viewModel = _umbracoMapper.Map(content)!; + + viewModel.Urls = await _contentUrlFactory.GetUrlsAsync(content); + + viewModel.TemplateKey = content.TemplateId.HasValue + ? _fileService.GetTemplate(content.TemplateId.Value)?.Key + : null; + + return viewModel; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/IAuditLogViewModelFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IAuditLogViewModelFactory.cs new file mode 100644 index 0000000000..798eb47e76 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/IAuditLogViewModelFactory.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Api.Management.ViewModels.AuditLogs; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.Factories; + +public interface IAuditLogViewModelFactory +{ + IEnumerable CreateAuditLogViewModel(IEnumerable auditItems); + + IEnumerable CreateAuditLogWithUsernameViewModels(IEnumerable auditItems); +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/IContentUrlFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IContentUrlFactory.cs new file mode 100644 index 0000000000..c48cb67052 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/IContentUrlFactory.cs @@ -0,0 +1,9 @@ +using Umbraco.Cms.Api.Management.ViewModels.Content; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.Factories; + +public interface IContentUrlFactory +{ + Task> GetUrlsAsync(IContent content); +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/IDocumentViewModelFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IDocumentViewModelFactory.cs new file mode 100644 index 0000000000..46da5746b6 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/IDocumentViewModelFactory.cs @@ -0,0 +1,9 @@ +using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.Factories; + +public interface IDocumentViewModelFactory +{ + Task CreateViewModelAsync(IContent content); +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/IPackageDefinitionFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IPackageDefinitionFactory.cs new file mode 100644 index 0000000000..e6c207004a --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/IPackageDefinitionFactory.cs @@ -0,0 +1,9 @@ +using Umbraco.Cms.Api.Management.ViewModels.Package; +using Umbraco.Cms.Core.Packaging; + +namespace Umbraco.Cms.Api.Management.Factories; + +public interface IPackageDefinitionFactory +{ + PackageDefinition CreatePackageDefinition(PackageCreateModel packageCreateModel); +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/IUserGroupViewModelFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/IUserGroupViewModelFactory.cs new file mode 100644 index 0000000000..b79fd7b9b3 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/IUserGroupViewModelFactory.cs @@ -0,0 +1,41 @@ +using Umbraco.Cms.Api.Management.ViewModels.UserGroups; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace Umbraco.Cms.Api.Management.Factories; + +/// +/// A factory for creating +/// +public interface IUserGroupViewModelFactory +{ + /// + /// Creates a based on a + /// + /// + /// + Task CreateAsync(IUserGroup userGroup); + + /// + /// Creates multiple base on multiple + /// + /// + /// + Task> CreateMultipleAsync(IEnumerable userGroups); + + /// + /// Creates an based on a + /// + /// + /// An attempt indicating if the operation was a success as well as a more detailed . + Task> CreateAsync(UserGroupSaveModel saveModel); + + /// + /// Converts the values of an update model to fit with the existing backoffice implementations, and maps it to an existing user group. + /// + /// Existing user group to map to. + /// Update model containing the new values. + /// An attempt indicating if the operation was a success as well as a more detailed . + Task> UpdateAsync(IUserGroup current, UserGroupUpdateModel update); +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/PackageDefinitionFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/PackageDefinitionFactory.cs new file mode 100644 index 0000000000..cce54c35d5 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/PackageDefinitionFactory.cs @@ -0,0 +1,25 @@ +using Umbraco.Cms.Api.Management.ViewModels.Package; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Packaging; + +namespace Umbraco.Cms.Api.Management.Factories; + +internal class PackageDefinitionFactory : IPackageDefinitionFactory +{ + private readonly IUmbracoMapper _umbracoMapper; + + public PackageDefinitionFactory(IUmbracoMapper umbracoMapper) => _umbracoMapper = umbracoMapper; + + public PackageDefinition CreatePackageDefinition(PackageCreateModel packageCreateModel) + { + // Macros are not included! + PackageDefinition packageDefinition = _umbracoMapper.Map(packageCreateModel)!; + + // Temp Id, PackageId and PackagePath for the newly created package + packageDefinition.Id = 0; + packageDefinition.PackageId = Guid.Empty; + packageDefinition.PackagePath = string.Empty; + + return packageDefinition; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Factories/UserGroupViewModelFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/UserGroupViewModelFactory.cs new file mode 100644 index 0000000000..e732497f3a --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Factories/UserGroupViewModelFactory.cs @@ -0,0 +1,226 @@ +using Umbraco.Cms.Api.Management.Mapping; +using Umbraco.Cms.Api.Management.ViewModels.UserGroups; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Factories; + +/// +public class UserGroupViewModelFactory : IUserGroupViewModelFactory +{ + private readonly IEntityService _entityService; + private readonly IShortStringHelper _shortStringHelper; + private readonly ILanguageService _languageService; + + public UserGroupViewModelFactory( + IEntityService entityService, + IShortStringHelper shortStringHelper, + ILanguageService languageService) + { + _entityService = entityService; + _shortStringHelper = shortStringHelper; + _languageService = languageService; + } + + /// + public async Task CreateAsync(IUserGroup userGroup) + { + Guid? contentStartNodeKey = GetKeyFromId(userGroup.StartContentId, UmbracoObjectTypes.Document); + Guid? mediaStartNodeKey = GetKeyFromId(userGroup.StartMediaId, UmbracoObjectTypes.Media); + Attempt, UserGroupOperationStatus> languageIsoCodesMappingAttempt = await MapLanguageIdsToIsoCodeAsync(userGroup.AllowedLanguages); + + // We've gotten this data from the database, so the mapping should not fail + if (languageIsoCodesMappingAttempt.Success is false) + { + throw new InvalidOperationException($"Unknown language ID in User Group: {userGroup.Name}"); + } + + return new UserGroupViewModel + { + Name = userGroup.Name ?? string.Empty, + Key = userGroup.Key, + DocumentStartNodeKey = contentStartNodeKey, + MediaStartNodeKey = mediaStartNodeKey, + Icon = userGroup.Icon, + Languages = languageIsoCodesMappingAttempt.Result, + HasAccessToAllLanguages = userGroup.HasAccessToAllLanguages, + Permissions = userGroup.PermissionNames, + Sections = userGroup.AllowedSections.Select(SectionMapper.GetName), + }; + } + + /// + public async Task> CreateMultipleAsync(IEnumerable userGroups) + { + var userGroupViewModels = new List(); + foreach (IUserGroup userGroup in userGroups) + { + userGroupViewModels.Add(await CreateAsync(userGroup)); + } + + return userGroupViewModels; + } + + /// + public async Task> CreateAsync(UserGroupSaveModel saveModel) + { + var cleanedName = saveModel.Name.CleanForXss('[', ']', '(', ')', ':'); + + var group = new UserGroup(_shortStringHelper) + { + Name = cleanedName, + Alias = cleanedName, + Icon = saveModel.Icon, + HasAccessToAllLanguages = saveModel.HasAccessToAllLanguages, + PermissionNames = saveModel.Permissions, + }; + + Attempt assignmentAttempt = AssignStartNodesToUserGroup(saveModel, group); + if (assignmentAttempt.Success is false) + { + return Attempt.FailWithStatus(assignmentAttempt.Result, group); + } + + foreach (var section in saveModel.Sections) + { + group.AddAllowedSection(SectionMapper.GetAlias(section)); + } + + Attempt, UserGroupOperationStatus> languageIsoCodeMappingAttempt = await MapLanguageIsoCodesToIdsAsync(saveModel.Languages); + if (languageIsoCodeMappingAttempt.Success is false) + { + return Attempt.FailWithStatus(languageIsoCodeMappingAttempt.Status, group); + } + + foreach (var languageId in languageIsoCodeMappingAttempt.Result) + { + group.AddAllowedLanguage(languageId); + } + + return Attempt.SucceedWithStatus(UserGroupOperationStatus.Success, group); + } + + /// + public async Task> UpdateAsync(IUserGroup current, UserGroupUpdateModel update) + { + Attempt assignmentAttempt = AssignStartNodesToUserGroup(update, current); + if (assignmentAttempt.Success is false) + { + return Attempt.FailWithStatus(assignmentAttempt.Result, current); + } + + current.ClearAllowedLanguages(); + Attempt, UserGroupOperationStatus> languageIdsMappingAttempt = await MapLanguageIsoCodesToIdsAsync(update.Languages); + if (languageIdsMappingAttempt.Success is false) + { + return Attempt.FailWithStatus(languageIdsMappingAttempt.Status, current); + } + + foreach (var languageId in languageIdsMappingAttempt.Result) + { + current.AddAllowedLanguage(languageId); + } + + current.ClearAllowedSections(); + foreach (var sectionName in update.Sections) + { + current.AddAllowedSection(SectionMapper.GetAlias(sectionName)); + } + + current.Name = update.Name.CleanForXss('[', ']', '(', ')', ':'); + current.Icon = update.Icon; + current.HasAccessToAllLanguages = update.HasAccessToAllLanguages; + current.PermissionNames = update.Permissions; + + + return Attempt.SucceedWithStatus(UserGroupOperationStatus.Success, current); + } + + private async Task, UserGroupOperationStatus>> MapLanguageIdsToIsoCodeAsync(IEnumerable ids) + { + IEnumerable languages = await _languageService.GetAllAsync(); + string[] isoCodes = languages + .Where(x => ids.Contains(x.Id)) + .Select(x => x.IsoCode) + .ToArray(); + + return isoCodes.Length == ids.Count() + ? Attempt.SucceedWithStatus, UserGroupOperationStatus>(UserGroupOperationStatus.Success, isoCodes) + : Attempt.FailWithStatus, UserGroupOperationStatus>(UserGroupOperationStatus.LanguageNotFound, isoCodes); + } + + private async Task, UserGroupOperationStatus>> MapLanguageIsoCodesToIdsAsync(IEnumerable isoCodes) + { + IEnumerable languages = await _languageService.GetAllAsync(); + int[] languageIds = languages + .Where(x => isoCodes.Contains(x.IsoCode)) + .Select(x => x.Id) + .ToArray(); + + return languageIds.Length == isoCodes.Count() + ? Attempt.SucceedWithStatus, UserGroupOperationStatus>(UserGroupOperationStatus.Success, languageIds) + : Attempt.FailWithStatus, UserGroupOperationStatus>(UserGroupOperationStatus.LanguageNotFound, languageIds); + } + + private Attempt AssignStartNodesToUserGroup(UserGroupBase source, IUserGroup target) + { + if (source.DocumentStartNodeKey is not null) + { + var contentId = GetIdFromKey(source.DocumentStartNodeKey.Value, UmbracoObjectTypes.Document); + + if (contentId is null) + { + return Attempt.Fail(UserGroupOperationStatus.DocumentStartNodeKeyNotFound); + } + + target.StartContentId = contentId; + } + + if (source.MediaStartNodeKey is not null) + { + var mediaId = GetIdFromKey(source.MediaStartNodeKey.Value, UmbracoObjectTypes.Media); + + if (mediaId is null) + { + return Attempt.Fail(UserGroupOperationStatus.MediaStartNodeKeyNotFound); + } + + target.StartMediaId = mediaId; + } + + return Attempt.Succeed(UserGroupOperationStatus.Success); + } + + private Guid? GetKeyFromId(int? id, UmbracoObjectTypes objectType) + { + if (id is null) + { + return null; + } + + Attempt attempt = _entityService.GetKey(id.Value, objectType); + if (attempt.Success is false) + { + return null; + } + + return attempt.Result; + } + + private int? GetIdFromKey(Guid key, UmbracoObjectTypes objectType) + { + Attempt attempt = _entityService.GetId(key, objectType); + + if (attempt.Success is false) + { + return null; + } + + return attempt.Result; + } +} diff --git a/src/Umbraco.Cms.Api.Management/ManagementApiComposer.cs b/src/Umbraco.Cms.Api.Management/ManagementApiComposer.cs index 56d67ae0df..1123847943 100644 --- a/src/Umbraco.Cms.Api.Management/ManagementApiComposer.cs +++ b/src/Umbraco.Cms.Api.Management/ManagementApiComposer.cs @@ -25,6 +25,9 @@ public class ManagementApiComposer : IComposer .AddUpgrader() .AddSearchManagement() .AddTrees() + .AddAuditLogs() + .AddDocuments() + .AddDocumentTypes() .AddLanguages() .AddDictionary() .AddFileUpload() @@ -35,6 +38,7 @@ public class ManagementApiComposer : IComposer .AddDataTypes() .AddTemplates() .AddLogViewer() + .AddUserGroups() .AddPackages() .AddBackOfficeAuthentication() .AddApiVersioning() diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Content/ContentMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Content/ContentMapDefinition.cs new file mode 100644 index 0000000000..5d381d74da --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/Content/ContentMapDefinition.cs @@ -0,0 +1,71 @@ +using Umbraco.Cms.Api.Management.ViewModels.Content; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Mapping.Content; + +public abstract class ContentMapDefinition + where TContent : IContentBase + where TValueViewModel : ValueViewModelBase, new() + where TVariantViewModel : VariantViewModelBase, new() +{ + private readonly PropertyEditorCollection _propertyEditorCollection; + + protected ContentMapDefinition(PropertyEditorCollection propertyEditorCollection) => _propertyEditorCollection = propertyEditorCollection; + + protected delegate void ValueViewModelMapping(IDataEditor propertyEditor, TValueViewModel variantViewModel); + + protected delegate void VariantViewModelMapping(string? culture, string? segment, TVariantViewModel variantViewModel); + + protected IEnumerable MapValueViewModels(TContent source, ValueViewModelMapping? additionalPropertyMapping = null) => + source + .Properties + .SelectMany(property => property + .Values + .Select(propertyValue => + { + IDataEditor? propertyEditor = _propertyEditorCollection[property.PropertyType.PropertyEditorAlias]; + if (propertyEditor == null) + { + return null; + } + + var variantViewModel = new TValueViewModel + { + Culture = propertyValue.Culture, + Segment = propertyValue.Segment, + Alias = property.Alias, + Value = propertyEditor.GetValueEditor().ToEditor(property, propertyValue.Culture, propertyValue.Segment) + }; + additionalPropertyMapping?.Invoke(propertyEditor, variantViewModel); + return variantViewModel; + })) + .WhereNotNull() + .ToArray(); + + protected IEnumerable MapVariantViewModels(TContent source, VariantViewModelMapping? additionalVariantMapping = null) + { + IPropertyValue[] propertyValues = source.Properties.SelectMany(propertyCollection => propertyCollection.Values).ToArray(); + var cultures = source.AvailableCultures.DefaultIfEmpty(null).ToArray(); + var segments = propertyValues.Select(property => property.Segment).Distinct().DefaultIfEmpty(null).ToArray(); + + return cultures + .SelectMany(culture => segments.Select(segment => + { + var variantViewModel = new TVariantViewModel + { + Culture = culture, + Segment = segment, + Name = source.GetCultureName(culture) ?? string.Empty, + CreateDate = source.CreateDate, // apparently there is no culture specific creation date + UpdateDate = culture == null + ? source.UpdateDate + : source.GetUpdateDate(culture) ?? source.UpdateDate, + }; + additionalVariantMapping?.Invoke(culture, segment, variantViewModel); + return variantViewModel; + })) + .ToArray(); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Mapping/ContentType/ContentTypeMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/ContentType/ContentTypeMapDefinition.cs new file mode 100644 index 0000000000..97dd0a8666 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/ContentType/ContentTypeMapDefinition.cs @@ -0,0 +1,78 @@ +using Umbraco.Cms.Api.Management.ViewModels.ContentType; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Api.Management.Mapping.ContentType; + +public abstract class ContentTypeMapDefinition + where TContentType : IContentTypeBase + where TPropertyTypeViewModel : PropertyTypeViewModelBase, new() + where TPropertyTypeContainerViewModel : PropertyTypeContainerViewModelBase, new() +{ + protected IEnumerable MapPropertyTypes(TContentType source) + { + // create a mapping table between properties and their associated groups + var groupKeysByPropertyKeys = source + .PropertyGroups + .SelectMany(propertyGroup => (propertyGroup.PropertyTypes?.ToArray() ?? Array.Empty()) + .Select(propertyType => new { GroupKey = propertyGroup.Key, PropertyTypeKey = propertyType.Key })) + .ToDictionary(map => map.PropertyTypeKey, map => map.GroupKey); + + return source.PropertyTypes.Select(propertyType => + new TPropertyTypeViewModel + { + Key = propertyType.Key, + ContainerKey = groupKeysByPropertyKeys.ContainsKey(propertyType.Key) + ? groupKeysByPropertyKeys[propertyType.Key] + : null, + Name = propertyType.Name, + Alias = propertyType.Alias, + Description = propertyType.Description, + DataTypeKey = propertyType.DataTypeKey, + VariesByCulture = propertyType.VariesByCulture(), + VariesBySegment = propertyType.VariesBySegment(), + Validation = new PropertyTypeValidation + { + Mandatory = propertyType.Mandatory, + MandatoryMessage = propertyType.MandatoryMessage, + RegEx = propertyType.ValidationRegExp, + RegExMessage = propertyType.ValidationRegExpMessage + }, + Appearance = new PropertyTypeAppearance + { + LabelOnTop = propertyType.LabelOnTop + } + }) + .ToArray(); + } + + protected IEnumerable MapPropertyTypeContainers(TContentType source) + { + // create a mapping table between property group aliases and keys + var groupKeysByGroupAliases = source + .PropertyGroups + .ToDictionary(propertyGroup => propertyGroup.Alias, propertyGroup => propertyGroup.Key); + + Guid? ParentGroupKey(PropertyGroup group) + { + var path = group.Alias.Split(Constants.CharArrays.ForwardSlash); + return path.Length == 1 || groupKeysByGroupAliases.TryGetValue(path.First(), out Guid parentGroupKey) == false + ? null + : parentGroupKey; + } + + return source + .PropertyGroups + .Select(propertyGroup => + new TPropertyTypeContainerViewModel + { + Key = propertyGroup.Key, + ParentKey = ParentGroupKey(propertyGroup), + Type = propertyGroup.Type.ToString(), + SortOrder = propertyGroup.SortOrder, + Name = propertyGroup.Name, + }) + .ToArray(); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Document/DocumentMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Document/DocumentMapDefinition.cs new file mode 100644 index 0000000000..4787688f21 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/Document/DocumentMapDefinition.cs @@ -0,0 +1,57 @@ +using Umbraco.Cms.Api.Management.Mapping.Content; +using Umbraco.Cms.Api.Management.ViewModels.Content; +using Umbraco.Cms.Api.Management.ViewModels.Document; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; + +namespace Umbraco.Cms.Api.Management.Mapping.Document; + +public class DocumentMapDefinition : ContentMapDefinition, IMapDefinition +{ + public DocumentMapDefinition(PropertyEditorCollection propertyEditorCollection) + : base(propertyEditorCollection) + { + } + + public void DefineMaps(IUmbracoMapper mapper) + => mapper.Define((_, _) => new DocumentViewModel(), Map); + + // Umbraco.Code.MapAll -Urls -TemplateKey + private void Map(IContent source, DocumentViewModel target, MapperContext context) + { + target.Key = source.Key; + target.ContentTypeKey = source.ContentType.Key; + target.Values = MapValueViewModels(source); + target.Variants = MapVariantViewModels( + source, + (culture, _, documentVariantViewModel) => + { + documentVariantViewModel.State = GetSavedState(source, culture); + documentVariantViewModel.PublishDate = culture == null + ? source.PublishDate + : source.GetPublishDate(culture); + }); + } + + private ContentState GetSavedState(IContent content, string? culture) + { + if (content.Id <= 0 || (culture != null && content.IsCultureAvailable(culture) == false)) + { + return ContentState.NotCreated; + } + + var isDraft = content.PublishedState == PublishedState.Unpublished || + (culture != null && content.IsCulturePublished(culture) == false); + if (isDraft) + { + return ContentState.Draft; + } + + var isEdited = culture != null + ? content.IsCultureEdited(culture) + : content.Edited; + + return isEdited ? ContentState.PublishedPendingChanges : ContentState.Published; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs new file mode 100644 index 0000000000..a5c5f63c53 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/DocumentType/DocumentTypeMapDefinition.cs @@ -0,0 +1,62 @@ +using Umbraco.Cms.Api.Management.Mapping.ContentType; +using Umbraco.Cms.Api.Management.ViewModels.ContentType; +using Umbraco.Cms.Api.Management.ViewModels.DocumentType; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Extensions; +using ContentTypeSort = Umbraco.Cms.Api.Management.ViewModels.ContentType.ContentTypeSort; + +namespace Umbraco.Cms.Api.Management.Mapping.DocumentType; + +public class DocumentTypeMapDefinition : ContentTypeMapDefinition, IMapDefinition +{ + public void DefineMaps(IUmbracoMapper mapper) + => mapper.Define((_, _) => new DocumentTypeViewModel(), Map); + + // Umbraco.Code.MapAll + private void Map(IContentType source, DocumentTypeViewModel target, MapperContext context) + { + target.Key = source.Key; + target.Alias = source.Alias; + target.Name = source.Name ?? string.Empty; + target.Description = source.Description; + target.Icon = source.Icon ?? string.Empty; + target.AllowedAsRoot = source.AllowedAsRoot; + target.VariesByCulture = source.VariesByCulture(); + target.VariesBySegment = source.VariesBySegment(); + target.IsElement = source.IsElement; + target.Containers = MapPropertyTypeContainers(source); + target.Properties = MapPropertyTypes(source); + + if (source.AllowedContentTypes != null) + { + target.AllowedContentTypes = source.AllowedContentTypes.Select(contentTypeSort + => new ContentTypeSort { Key = contentTypeSort.Key, SortOrder = contentTypeSort.SortOrder }); + } + + if (source.AllowedTemplates != null) + { + target.AllowedTemplateKeys = source.AllowedTemplates.Select(template => template.Key); + } + + target.DefaultTemplateKey = source.DefaultTemplate?.Key; + + if (source.HistoryCleanup != null) + { + target.Cleanup = new ContentTypeCleanup + { + PreventCleanup = source.HistoryCleanup.PreventCleanup, + KeepAllVersionsNewerThanDays = source.HistoryCleanup.KeepAllVersionsNewerThanDays, + KeepLatestVersionPerDayForDays = source.HistoryCleanup.KeepLatestVersionPerDayForDays + }; + } + + target.Compositions = source.ContentTypeComposition.Select(contentType => new ContentTypeComposition + { + Key = contentType.Key, + CompositionType = contentType.Id == source.ParentId + ? ContentTypeCompositionType.Inheritance + : ContentTypeCompositionType.Composition + }).ToArray(); + } +} diff --git a/src/Umbraco.Cms.Api.Management/Mapping/Package/PackageViewModelMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/Package/PackageViewModelMapDefinition.cs new file mode 100644 index 0000000000..873848c356 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/Package/PackageViewModelMapDefinition.cs @@ -0,0 +1,83 @@ +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.ViewModels.Package; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Packaging; + +namespace Umbraco.Cms.Api.Management.Mapping.Package; + +public class PackageViewModelMapDefinition : IMapDefinition +{ + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define((_, _) => new PackageDefinition(), Map); + mapper.Define( + (_, _) => new PackageDefinitionViewModel + { + Name = string.Empty, + PackagePath = string.Empty + }, + Map); + mapper.Define((_, _) => new PackageMigrationStatusViewModel { PackageName = string.Empty }, Map); + mapper.Define, PagedViewModel>((_, _) => new PagedViewModel(), Map); + } + + // Umbraco.Code.MapAll -Id -PackageId -PackagePath -Macros + private static void Map(PackageModelBase source, PackageDefinition target, MapperContext context) + { + target.Name = source.Name; + target.ContentLoadChildNodes = source.ContentLoadChildNodes; + target.ContentNodeId = source.ContentNodeId; + target.Languages = source.Languages; + target.DictionaryItems = source.DictionaryItems; + target.Templates = source.Templates; + target.PartialViews = source.PartialViews; + target.DocumentTypes = source.DocumentTypes; + target.MediaTypes = source.MediaTypes; + target.Stylesheets = source.Stylesheets; + target.Scripts = source.Scripts; + target.DataTypes = source.DataTypes; + target.MediaUdis = source.MediaKeys.Select(x => new GuidUdi(Constants.UdiEntityType.Media, x)).ToList(); + target.MediaLoadChildNodes = source.MediaLoadChildNodes; + } + + // Umbraco.Code.MapAll + private static void Map(PackageDefinition source, PackageDefinitionViewModel target, MapperContext context) + { + target.Key = source.PackageId; + target.Name = source.Name; + target.PackagePath = source.PackagePath; + target.ContentNodeId = source.ContentNodeId; + target.ContentLoadChildNodes = source.ContentLoadChildNodes; + target.MediaKeys = source.MediaUdis.Select(x => x.Guid).ToList(); + target.MediaLoadChildNodes = source.MediaLoadChildNodes; + target.DocumentTypes = source.DocumentTypes; + target.MediaTypes = source.MediaTypes; + target.DataTypes = source.DataTypes; + target.Templates = source.Templates; + target.PartialViews = source.PartialViews; + target.Stylesheets = source.Stylesheets; + target.Scripts = source.Scripts; + target.Languages = source.Languages; + target.DictionaryItems = source.DictionaryItems; + } + + // Umbraco.Code.MapAll + private static void Map(InstalledPackage source, PackageMigrationStatusViewModel target, MapperContext context) + { + if (source.PackageName is not null) + { + target.PackageName = source.PackageName; + } + + target.HasPendingMigrations = source.HasPendingMigrations; + } + + // Umbraco.Code.MapAll + private static void Map(IEnumerable source, PagedViewModel target, MapperContext context) + { + PackageDefinition[] definitions = source.ToArray(); + target.Items = context.MapEnumerable(definitions); + target.Total = definitions.Length; + } +} diff --git a/src/Umbraco.Cms.Api.Management/Mapping/SectionMapper.cs b/src/Umbraco.Cms.Api.Management/Mapping/SectionMapper.cs new file mode 100644 index 0000000000..41db2123e3 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/Mapping/SectionMapper.cs @@ -0,0 +1,54 @@ +namespace Umbraco.Cms.Api.Management.Mapping; + +/// +/// Maps from the old section aliases to the new section names. +/// This is static since it's expected to be removed, so might as well make the clean up work as easy as possible. +/// FIXME: This is a temporary thing until permissions is fleshed out and section is either migrated to some form of permission +/// +public static class SectionMapper +{ + private static readonly List _sectionMappings = new() + { + new SectionMapping { Alias = "content", Name = "Umb.Section.Content" }, + new SectionMapping { Alias = "media", Name = "Umb.Section.Media" }, + new SectionMapping { Alias = "member", Name = "Umb.Section.Members" }, + new SectionMapping { Alias = "settings", Name = "Umb.Section.Settings" }, + new SectionMapping { Alias = "packages", Name = "Umb.Section.Packages" }, + new SectionMapping { Alias = "translation", Name = "Umb.Section.Translation" }, + new SectionMapping { Alias = "users", Name = "Umb.Section.Users" }, + new SectionMapping { Alias = "forms", Name = "Umb.Section.Forms" }, + }; + + public static string GetName(string alias) + { + SectionMapping? mapping = _sectionMappings.FirstOrDefault(x => x.Alias == alias); + + if (mapping is not null) + { + return mapping.Name; + } + + // If we can't find it we just fall back to the alias + return alias; + } + + public static string GetAlias(string name) + { + SectionMapping? mapping = _sectionMappings.FirstOrDefault(x => x.Name == name); + + if (mapping is not null) + { + return mapping.Alias; + } + + // If we can't find it we just fall back to the name + return name; + } + + private class SectionMapping + { + public required string Alias { get; init; } + + public required string Name { get; init; } + } +} diff --git a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs index 9b36669fcf..f7b75cc692 100644 --- a/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs +++ b/src/Umbraco.Cms.Api.Management/Mapping/TrackedReferences/TrackedReferenceViewModelsMapDefinition.cs @@ -23,6 +23,7 @@ public class TrackedReferenceViewModelsMapDefinition : IMapDefinition target.RelationTypeIsBidirectional = source.RelationTypeIsBidirectional; target.RelationTypeIsDependency = source.RelationTypeIsDependency; target.RelationTypeName = source.RelationTypeName; + target.NodePublished = source.NodePublished; } } diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index 328914ed1f..52824318c7 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -6,6 +6,181 @@ "version": "1.0" }, "paths": { + "/umbraco/management/api/v1/audit-log": { + "get": { + "tags": [ + "Audit Log" + ], + "operationId": "GetAuditLog", + "parameters": [ + { + "name": "orderDirection", + "in": "query", + "schema": { + "$ref": "#/components/schemas/DirectionModel" + } + }, + { + "name": "sinceDate", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "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/PagedAuditLogWithUsernameResponseModel" + } + } + } + } + } + } + }, + "/umbraco/management/api/v1/audit-log/{key}": { + "get": { + "tags": [ + "Audit Log" + ], + "operationId": "GetAuditLogByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "orderDirection", + "in": "query", + "schema": { + "$ref": "#/components/schemas/DirectionModel" + } + }, + { + "name": "sinceDate", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "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/PagedAuditLogResponseModel" + } + } + } + } + } + } + }, + "/umbraco/management/api/v1/audit-log/type/{logType}": { + "get": { + "tags": [ + "Audit Log" + ], + "operationId": "GetAuditLogTypeByLogType", + "parameters": [ + { + "name": "logType", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/AuditTypeModel" + } + }, + { + "name": "sinceDate", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "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/PagedAuditLogResponseModel" + } + } + } + } + } + } + }, "/umbraco/management/api/v1/culture": { "get": { "tags": [ @@ -1239,6 +1414,44 @@ } } }, + "/umbraco/management/api/v1/document-type/{key}": { + "get": { + "tags": [ + "Document Type" + ], + "operationId": "GetDocumentTypeByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentTypeModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found" + } + } + } + }, "/umbraco/management/api/v1/tree/document-type/children": { "get": { "tags": [ @@ -1383,6 +1596,44 @@ } } }, + "/umbraco/management/api/v1/document/{key}": { + "get": { + "tags": [ + "Document" + ], + "operationId": "GetDocumentByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found" + } + } + } + }, "/umbraco/management/api/v1/recycle-bin/document/children": { "get": { "tags": [ @@ -2417,7 +2668,18 @@ } }, "200": { - "description": "Success" + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/LogLevelCountsModel" + } + ] + } + } + } } } } @@ -3411,6 +3673,305 @@ } } }, + "/umbraco/management/api/v1/package/{name}/run-migration": { + "post": { + "tags": [ + "Package" + ], + "operationId": "PostPackageByNameRunMigration", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "404": { + "description": "Not Found" + }, + "409": { + "description": "Conflict", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + }, + "200": { + "description": "Success" + } + } + } + }, + "/umbraco/management/api/v1/package/created": { + "get": { + "tags": [ + "Package" + ], + "operationId": "GetPackageCreated", + "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/PagedPackageDefinitionModel" + } + } + } + } + } + }, + "post": { + "tags": [ + "Package" + ], + "operationId": "PostPackageCreated", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PackageCreateModel" + } + ] + } + } + } + }, + "responses": { + "404": { + "description": "Not Found" + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + }, + "201": { + "description": "Created", + "headers": { + "Location": { + "description": "Location of the newly created resource", + "schema": { + "type": "string", + "description": "Location of the newly created resource", + "format": "uri" + } + } + } + } + } + } + }, + "/umbraco/management/api/v1/package/created/{key}": { + "get": { + "tags": [ + "Package" + ], + "operationId": "GetPackageCreatedByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "404": { + "description": "Not Found" + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PackageDefinitionModel" + } + ] + } + } + } + } + } + }, + "delete": { + "tags": [ + "Package" + ], + "operationId": "DeletePackageCreatedByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "404": { + "description": "Not Found" + }, + "200": { + "description": "Success" + } + } + }, + "put": { + "tags": [ + "Package" + ], + "operationId": "PutPackageCreatedByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/PackageUpdateModel" + } + ] + } + } + } + }, + "responses": { + "404": { + "description": "Not Found" + }, + "200": { + "description": "Success" + } + } + } + }, + "/umbraco/management/api/v1/package/created/{key}/download": { + "get": { + "tags": [ + "Package" + ], + "operationId": "GetPackageCreatedByKeyDownload", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "404": { + "description": "Not Found" + }, + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/umbraco/management/api/v1/package/migration-status": { + "get": { + "tags": [ + "Package" + ], + "operationId": "GetPackageMigrationStatus", + "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/PagedPackageMigrationStatusModel" + } + } + } + } + } + } + }, "/umbraco/management/api/v1/tree/partial-view/children": { "get": { "tags": [ @@ -3559,6 +4120,30 @@ } } } + }, + "put": { + "tags": [ + "Profiling" + ], + "operationId": "PutProfilingStatus", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ProfilingStatusModel" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success" + } + } } }, "/umbraco/management/api/v1/published-cache/collect": { @@ -5032,20 +5617,20 @@ } } }, - "/umbraco/management/api/v1/tracked-reference/{id}": { + "/umbraco/management/api/v1/tracked-reference/{key}": { "get": { "tags": [ "Tracked Reference" ], - "operationId": "GetTrackedReferenceById", + "operationId": "GetTrackedReferenceByKey", "parameters": [ { - "name": "id", + "name": "key", "in": "path", "required": true, "schema": { - "type": "integer", - "format": "int32" + "type": "string", + "format": "uuid" } }, { @@ -5053,7 +5638,8 @@ "in": "query", "schema": { "type": "integer", - "format": "int64" + "format": "int64", + "default": 0 } }, { @@ -5061,14 +5647,16 @@ "in": "query", "schema": { "type": "integer", - "format": "int64" + "format": "int64", + "default": 20 } }, { "name": "filterMustBeIsDependency", "in": "query", "schema": { - "type": "boolean" + "type": "boolean", + "default": false } } ], @@ -5086,20 +5674,20 @@ } } }, - "/umbraco/management/api/v1/tracked-reference/descendants/{parentId}": { + "/umbraco/management/api/v1/tracked-reference/descendants/{parentKey}": { "get": { "tags": [ "Tracked Reference" ], - "operationId": "GetTrackedReferenceDescendantsByParentId", + "operationId": "GetTrackedReferenceDescendantsByParentKey", "parameters": [ { - "name": "parentId", + "name": "parentKey", "in": "path", "required": true, "schema": { - "type": "integer", - "format": "int32" + "type": "string", + "format": "uuid" } }, { @@ -5122,7 +5710,8 @@ "name": "filterMustBeIsDependency", "in": "query", "schema": { - "type": "boolean" + "type": "boolean", + "default": true } } ], @@ -5148,13 +5737,14 @@ "operationId": "GetTrackedReferenceItem", "parameters": [ { - "name": "ids", + "name": "key", "in": "query", "schema": { + "uniqueItems": true, "type": "array", "items": { - "type": "integer", - "format": "int32" + "type": "string", + "format": "uuid" } } }, @@ -5163,7 +5753,8 @@ "in": "query", "schema": { "type": "integer", - "format": "int64" + "format": "int64", + "default": 0 } }, { @@ -5171,14 +5762,16 @@ "in": "query", "schema": { "type": "integer", - "format": "int64" + "format": "int64", + "default": 20 } }, { "name": "filterMustBeIsDependency", "in": "query", "schema": { - "type": "boolean" + "type": "boolean", + "default": true } } ], @@ -5262,10 +5855,290 @@ } } } + }, + "/umbraco/management/api/v1/user-groups": { + "post": { + "tags": [ + "User Groups" + ], + "operationId": "PostUserGroups", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserGroupSaveModel" + } + ] + } + } + } + }, + "responses": { + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetailsModel" + } + } + } + }, + "201": { + "description": "Created", + "headers": { + "Location": { + "description": "Location of the newly created resource", + "schema": { + "type": "string", + "description": "Location of the newly created resource", + "format": "uri" + } + } + } + } + } + }, + "get": { + "tags": [ + "User Groups" + ], + "operationId": "GetUserGroups", + "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/PagedUserGroupModel" + } + } + } + } + } + } + }, + "/umbraco/management/api/v1/user-groups/{key}": { + "get": { + "tags": [ + "User Groups" + ], + "operationId": "GetUserGroupsByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserGroupModel" + } + ] + } + } + } + }, + "404": { + "description": "Not Found" + } + } + }, + "delete": { + "tags": [ + "User Groups" + ], + "operationId": "DeleteUserGroupsByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success" + }, + "404": { + "description": "Not Found" + } + } + }, + "put": { + "tags": [ + "User Groups" + ], + "operationId": "PutUserGroupsByKey", + "parameters": [ + { + "name": "key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserGroupUpdateModel" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "404": { + "description": "Not Found" + } + } + } } }, "components": { "schemas": { + "AuditLogBaseModel": { + "type": "object", + "properties": { + "userKey": { + "type": "string", + "format": "uuid" + }, + "entityKey": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "logType": { + "$ref": "#/components/schemas/AuditTypeModel" + }, + "entityType": { + "type": "string", + "nullable": true + }, + "comment": { + "type": "string", + "nullable": true + }, + "parameters": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "AuditLogResponseModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/AuditLogBaseModel" + } + ], + "additionalProperties": false + }, + "AuditLogWithUsernameResponseModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/AuditLogBaseModel" + } + ], + "properties": { + "userName": { + "type": "string", + "nullable": true + }, + "userAvatars": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "AuditTypeModel": { + "enum": [ + "New", + "Save", + "SaveVariant", + "Open", + "Delete", + "Publish", + "PublishVariant", + "SendToPublish", + "SendToPublishVariant", + "Unpublish", + "UnpublishVariant", + "Move", + "Copy", + "AssignDomain", + "PublicAccess", + "Sort", + "Notify", + "System", + "RollBack", + "PackagerInstall", + "PackagerUninstall", + "Custom", + "ContentVersionPreventCleanup", + "ContentVersionEnableCleanup" + ], + "type": "integer", + "format": "int32" + }, "ConsentLevelModel": { "type": "object", "properties": { @@ -5278,7 +6151,20 @@ }, "additionalProperties": false }, + "ContentStateModel": { + "enum": [ + "NotCreated", + "Draft", + "Published", + "PublishedPendingChanges" + ], + "type": "integer", + "format": "int32" + }, "ContentTreeItemModel": { + "required": [ + "$type" + ], "type": "object", "allOf": [ { @@ -5286,6 +6172,9 @@ } ], "properties": { + "$type": { + "type": "string" + }, "noAccess": { "type": "boolean" }, @@ -5293,6 +6182,196 @@ "type": "boolean" } }, + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "ContentTreeItemViewModel": "#/components/schemas/ContentTreeItemModel", + "DocumentTreeItemViewModel": "#/components/schemas/DocumentTreeItemModel" + } + } + }, + "ContentTypeCleanupModel": { + "type": "object", + "properties": { + "preventCleanup": { + "type": "boolean" + }, + "keepAllVersionsNewerThanDays": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "keepLatestVersionPerDayForDays": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false + }, + "ContentTypeCompositionModel": { + "type": "object", + "properties": { + "key": { + "type": "string", + "format": "uuid" + }, + "compositionType": { + "$ref": "#/components/schemas/ContentTypeCompositionTypeModel" + } + }, + "additionalProperties": false + }, + "ContentTypeCompositionTypeModel": { + "enum": [ + "Composition", + "Inheritance" + ], + "type": "integer", + "format": "int32" + }, + "ContentTypeSortModel": { + "type": "object", + "properties": { + "key": { + "type": "string", + "format": "uuid" + }, + "sortOrder": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "ContentTypeViewModelBaseDocumentTypePropertyTypeDocumentTypePropertyTypeContainerModel": { + "type": "object", + "properties": { + "key": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "icon": { + "type": "string" + }, + "allowedAsRoot": { + "type": "boolean" + }, + "variesByCulture": { + "type": "boolean" + }, + "variesBySegment": { + "type": "boolean" + }, + "isElement": { + "type": "boolean" + }, + "properties": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentTypePropertyTypeModel" + } + ] + } + }, + "containers": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentTypePropertyTypeContainerModel" + } + ] + } + }, + "allowedContentTypes": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/ContentTypeSortModel" + } + ] + } + }, + "compositions": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/ContentTypeCompositionModel" + } + ] + } + }, + "cleanup": { + "oneOf": [ + { + "$ref": "#/components/schemas/ContentTypeCleanupModel" + } + ] + } + }, + "additionalProperties": false + }, + "ContentUrlInfoModel": { + "type": "object", + "properties": { + "culture": { + "type": "string", + "nullable": true + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ContentViewModelBaseDocumentValueDocumentVariantModel": { + "type": "object", + "properties": { + "key": { + "type": "string", + "format": "uuid" + }, + "contentTypeKey": { + "type": "string", + "format": "uuid" + }, + "values": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentValueModel" + } + ] + } + }, + "variants": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/DocumentVariantModel" + } + ] + } + } + }, "additionalProperties": false }, "CultureModel": { @@ -5335,6 +6414,9 @@ "additionalProperties": false }, "DataTypeModel": { + "required": [ + "$type" + ], "type": "object", "allOf": [ { @@ -5342,6 +6424,9 @@ } ], "properties": { + "$type": { + "type": "string" + }, "key": { "type": "string", "format": "uuid" @@ -5352,7 +6437,13 @@ "nullable": true } }, - "additionalProperties": false + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "DataTypeViewModel": "#/components/schemas/DataTypeModel" + } + } }, "DataTypeModelBaseModel": { "type": "object", @@ -5560,6 +6651,9 @@ "additionalProperties": false }, "DictionaryItemModel": { + "required": [ + "$type" + ], "type": "object", "allOf": [ { @@ -5567,12 +6661,21 @@ } ], "properties": { + "$type": { + "type": "string" + }, "key": { "type": "string", "format": "uuid" } }, - "additionalProperties": false + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "DictionaryItemViewModel": "#/components/schemas/DictionaryItemModel" + } + } }, "DictionaryItemModelBaseModel": { "type": "object", @@ -5698,6 +6801,9 @@ "format": "int32" }, "DocumentBlueprintTreeItemModel": { + "required": [ + "$type" + ], "type": "object", "allOf": [ { @@ -5705,6 +6811,9 @@ } ], "properties": { + "$type": { + "type": "string" + }, "documentTypeKey": { "type": "string", "format": "uuid" @@ -5717,9 +6826,44 @@ "nullable": true } }, + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "DocumentBlueprintTreeItemViewModel": "#/components/schemas/DocumentBlueprintTreeItemModel" + } + } + }, + "DocumentModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/ContentViewModelBaseDocumentValueDocumentVariantModel" + } + ], + "properties": { + "urls": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/ContentUrlInfoModel" + } + ] + } + }, + "templateKey": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, "additionalProperties": false }, "DocumentTreeItemModel": { + "required": [ + "$type" + ], "type": "object", "allOf": [ { @@ -5727,6 +6871,9 @@ } ], "properties": { + "$type": { + "type": "string" + }, "isProtected": { "type": "boolean" }, @@ -5737,9 +6884,59 @@ "type": "boolean" } }, + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "DocumentTreeItemViewModel": "#/components/schemas/DocumentTreeItemModel" + } + } + }, + "DocumentTypeModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/ContentTypeViewModelBaseDocumentTypePropertyTypeDocumentTypePropertyTypeContainerModel" + } + ], + "properties": { + "allowedTemplateKeys": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "defaultTemplateKey": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "additionalProperties": false + }, + "DocumentTypePropertyTypeContainerModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/PropertyTypeContainerViewModelBaseModel" + } + ], + "additionalProperties": false + }, + "DocumentTypePropertyTypeModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/PropertyTypeViewModelBaseModel" + } + ], "additionalProperties": false }, "DocumentTypeTreeItemModel": { + "required": [ + "$type" + ], "type": "object", "allOf": [ { @@ -5747,13 +6944,53 @@ } ], "properties": { + "$type": { + "type": "string" + }, "isElement": { "type": "boolean" } }, + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "DocumentTypeTreeItemViewModel": "#/components/schemas/DocumentTypeTreeItemModel" + } + } + }, + "DocumentValueModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/ValueViewModelBaseModel" + } + ], + "additionalProperties": false + }, + "DocumentVariantModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/VariantViewModelBaseModel" + } + ], + "properties": { + "state": { + "$ref": "#/components/schemas/ContentStateModel" + }, + "publishDate": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, "additionalProperties": false }, "EntityTreeItemModel": { + "required": [ + "$type" + ], "type": "object", "allOf": [ { @@ -5761,6 +6998,9 @@ } ], "properties": { + "$type": { + "type": "string" + }, "key": { "type": "string", "format": "uuid" @@ -5774,7 +7014,18 @@ "nullable": true } }, - "additionalProperties": false + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "EntityTreeItemViewModel": "#/components/schemas/EntityTreeItemModel", + "ContentTreeItemViewModel": "#/components/schemas/ContentTreeItemModel", + "DocumentBlueprintTreeItemViewModel": "#/components/schemas/DocumentBlueprintTreeItemModel", + "DocumentTreeItemViewModel": "#/components/schemas/DocumentTreeItemModel", + "DocumentTypeTreeItemViewModel": "#/components/schemas/DocumentTypeTreeItemModel", + "FolderTreeItemViewModel": "#/components/schemas/FolderTreeItemModel" + } + } }, "FieldModel": { "type": "object", @@ -5825,6 +7076,9 @@ "additionalProperties": false }, "FolderModel": { + "required": [ + "$type" + ], "type": "object", "allOf": [ { @@ -5832,6 +7086,9 @@ } ], "properties": { + "$type": { + "type": "string" + }, "key": { "type": "string", "format": "uuid" @@ -5842,7 +7099,13 @@ "nullable": true } }, - "additionalProperties": false + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "FolderViewModel": "#/components/schemas/FolderModel" + } + } }, "FolderModelBaseModel": { "type": "object", @@ -5854,6 +7117,9 @@ "additionalProperties": false }, "FolderTreeItemModel": { + "required": [ + "$type" + ], "type": "object", "allOf": [ { @@ -5861,11 +7127,21 @@ } ], "properties": { + "$type": { + "type": "string" + }, "isFolder": { "type": "boolean" } }, - "additionalProperties": false + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "FolderTreeItemViewModel": "#/components/schemas/FolderTreeItemModel", + "DocumentTypeTreeItemViewModel": "#/components/schemas/DocumentTypeTreeItemModel" + } + } }, "FolderUpdateModel": { "type": "object", @@ -6211,6 +7487,32 @@ ], "additionalProperties": false }, + "LogLevelCountsModel": { + "type": "object", + "properties": { + "information": { + "type": "integer", + "format": "int32" + }, + "debug": { + "type": "integer", + "format": "int32" + }, + "warning": { + "type": "integer", + "format": "int32" + }, + "error": { + "type": "integer", + "format": "int32" + }, + "fatal": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, "LogLevelModel": { "enum": [ "Verbose", @@ -6379,6 +7681,187 @@ "type": "integer", "format": "int32" }, + "PackageCreateModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/PackageModelBaseModel" + } + ], + "additionalProperties": false + }, + "PackageDefinitionModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/PackageModelBaseModel" + } + ], + "properties": { + "key": { + "type": "string", + "format": "uuid" + }, + "packagePath": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PackageMigrationStatusModel": { + "type": "object", + "properties": { + "packageName": { + "type": "string" + }, + "hasPendingMigrations": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "PackageModelBaseModel": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "contentNodeId": { + "type": "string", + "nullable": true + }, + "contentLoadChildNodes": { + "type": "boolean" + }, + "mediaKeys": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "mediaLoadChildNodes": { + "type": "boolean" + }, + "documentTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "mediaTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "dataTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "templates": { + "type": "array", + "items": { + "type": "string" + } + }, + "partialViews": { + "type": "array", + "items": { + "type": "string" + } + }, + "stylesheets": { + "type": "array", + "items": { + "type": "string" + } + }, + "scripts": { + "type": "array", + "items": { + "type": "string" + } + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "dictionaryItems": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "PackageUpdateModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/PackageModelBaseModel" + } + ], + "properties": { + "packagePath": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PagedAuditLogResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/AuditLogResponseModel" + } + ] + } + } + }, + "additionalProperties": false + }, + "PagedAuditLogWithUsernameResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/AuditLogWithUsernameResponseModel" + } + ] + } + } + }, + "additionalProperties": false + }, "PagedContentTreeItemModel": { "required": [ "items", @@ -6787,6 +8270,54 @@ }, "additionalProperties": false }, + "PagedPackageDefinitionModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/PackageDefinitionModel" + } + ] + } + } + }, + "additionalProperties": false + }, + "PagedPackageMigrationStatusModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/PackageMigrationStatusModel" + } + ] + } + } + }, + "additionalProperties": false + }, "PagedRecycleBinItemModel": { "required": [ "items", @@ -6979,6 +8510,30 @@ }, "additionalProperties": false }, + "PagedUserGroupModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserGroupModel" + } + ] + } + } + }, + "additionalProperties": false + }, "ProblemDetailsModel": { "type": "object", "properties": { @@ -7015,9 +8570,120 @@ }, "additionalProperties": false }, - "RecycleBinItemModel": { + "PropertyTypeAppearanceModel": { "type": "object", "properties": { + "labelOnTop": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "PropertyTypeContainerViewModelBaseModel": { + "type": "object", + "properties": { + "key": { + "type": "string", + "format": "uuid" + }, + "parentKey": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "name": { + "type": "string", + "nullable": true + }, + "type": { + "type": "string" + }, + "sortOrder": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "PropertyTypeValidationModel": { + "type": "object", + "properties": { + "mandatory": { + "type": "boolean" + }, + "mandatoryMessage": { + "type": "string", + "nullable": true + }, + "regEx": { + "type": "string", + "nullable": true + }, + "regExMessage": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "PropertyTypeViewModelBaseModel": { + "type": "object", + "properties": { + "key": { + "type": "string", + "format": "uuid" + }, + "containerKey": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "alias": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "dataTypeKey": { + "type": "string", + "format": "uuid" + }, + "variesByCulture": { + "type": "boolean" + }, + "variesBySegment": { + "type": "boolean" + }, + "validation": { + "oneOf": [ + { + "$ref": "#/components/schemas/PropertyTypeValidationModel" + } + ] + }, + "appearance": { + "oneOf": [ + { + "$ref": "#/components/schemas/PropertyTypeAppearanceModel" + } + ] + } + }, + "additionalProperties": false + }, + "RecycleBinItemModel": { + "required": [ + "$type" + ], + "type": "object", + "properties": { + "$type": { + "type": "string" + }, "key": { "type": "string", "format": "uuid" @@ -7043,7 +8709,13 @@ "nullable": true } }, - "additionalProperties": false + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "RecycleBinItemViewModel": "#/components/schemas/RecycleBinItemModel" + } + } }, "RedirectStatusModel": { "enum": [ @@ -7108,6 +8780,10 @@ "type": "string", "nullable": true }, + "nodePublished": { + "type": "boolean", + "nullable": true + }, "contentTypeIcon": { "type": "string", "nullable": true @@ -7271,6 +8947,9 @@ "additionalProperties": false }, "TemplateModel": { + "required": [ + "$type" + ], "type": "object", "allOf": [ { @@ -7278,12 +8957,21 @@ } ], "properties": { + "$type": { + "type": "string" + }, "key": { "type": "string", "format": "uuid" } }, - "additionalProperties": false + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "TemplateViewModel": "#/components/schemas/TemplateModel" + } + } }, "TemplateModelBaseModel": { "type": "object", @@ -7532,6 +9220,96 @@ }, "additionalProperties": false }, + "UserGroupBaseModel": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "icon": { + "type": "string", + "nullable": true + }, + "sections": { + "type": "array", + "items": { + "type": "string" + } + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "hasAccessToAllLanguages": { + "type": "boolean" + }, + "documentStartNodeKey": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "mediaStartNodeKey": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "permissions": { + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "UserGroupModel": { + "required": [ + "$type" + ], + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/UserGroupBaseModel" + } + ], + "properties": { + "$type": { + "type": "string" + }, + "key": { + "type": "string", + "format": "uuid" + } + }, + "additionalProperties": false, + "discriminator": { + "propertyName": "$type", + "mapping": { + "UserGroupViewModel": "#/components/schemas/UserGroupModel" + } + } + }, + "UserGroupSaveModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/UserGroupBaseModel" + } + ], + "additionalProperties": false + }, + "UserGroupUpdateModel": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/UserGroupBaseModel" + } + ], + "additionalProperties": false + }, "UserInstallModel": { "required": [ "email", @@ -7585,6 +9363,51 @@ }, "additionalProperties": false }, + "ValueViewModelBaseModel": { + "type": "object", + "properties": { + "culture": { + "type": "string", + "nullable": true + }, + "segment": { + "type": "string", + "nullable": true + }, + "alias": { + "type": "string" + }, + "value": { + "nullable": true + } + }, + "additionalProperties": false + }, + "VariantViewModelBaseModel": { + "type": "object", + "properties": { + "culture": { + "type": "string", + "nullable": true + }, + "segment": { + "type": "string", + "nullable": true + }, + "name": { + "type": "string" + }, + "createDate": { + "type": "string", + "format": "date-time" + }, + "updateDate": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, "VersionModel": { "type": "object", "properties": { diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogBaseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogBaseModel.cs new file mode 100644 index 0000000000..7168fd9dfd --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogBaseModel.cs @@ -0,0 +1,20 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.ViewModels.AuditLogs; + +public class AuditLogBaseModel +{ + public Guid UserKey { get; set; } + + public Guid? EntityKey { get; set; } + + public DateTime Timestamp { get; set; } + + public AuditType LogType { get; set; } + + public string? EntityType { get; set; } + + public string? Comment { get; set; } + + public string? Parameters { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogResponseModel.cs new file mode 100644 index 0000000000..7b0f43b4e1 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogResponseModel.cs @@ -0,0 +1,8 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.ViewModels.AuditLogs; + +/// +public class AuditLogResponseModel : AuditLogBaseModel +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogWithUsernameResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogWithUsernameResponseModel.cs new file mode 100644 index 0000000000..d1b0933ae7 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/AuditLogs/AuditLogWithUsernameResponseModel.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Api.Management.ViewModels.AuditLogs; + +public class AuditLogWithUsernameResponseModel : AuditLogBaseModel +{ + public string? UserName { get; set; } + + public string[]? UserAvatars { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentState.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentState.cs new file mode 100644 index 0000000000..3bee0274d5 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentState.cs @@ -0,0 +1,27 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Content; + +/// +/// The saved state of a content item +/// +public enum ContentState +{ + /// + /// The item isn't created yet + /// + NotCreated = 1, + + /// + /// The item is saved but isn't published + /// + Draft = 2, + + /// + /// The item is published and there are no pending changes + /// + Published = 3, + + /// + /// The item is published and there are pending changes + /// + PublishedPendingChanges = 4, +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentUrlInfo.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentUrlInfo.cs new file mode 100644 index 0000000000..87c233f593 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentUrlInfo.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Content; + +public class ContentUrlInfo +{ + public required string? Culture { get; init; } + + public required string Url { get; init; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentViewModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentViewModelBase.cs new file mode 100644 index 0000000000..8274e0a3e5 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Content/ContentViewModelBase.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Content; + +public abstract class ContentViewModelBase + where TValueViewModelBase : ValueViewModelBase + where TVariantViewModel : VariantViewModelBase +{ + public Guid Key { get; set; } + + public Guid ContentTypeKey { get; set; } + + public IEnumerable Values { get; set; } = Array.Empty(); + + public IEnumerable Variants { get; set; } = Array.Empty(); +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Content/ValueViewModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Content/ValueViewModelBase.cs new file mode 100644 index 0000000000..9dacaa9f2b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Content/ValueViewModelBase.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Content; + +public abstract class ValueViewModelBase +{ + public string? Culture { get; set; } + + public string? Segment { get; set; } + + public string Alias { get; set; } = string.Empty; + + public object? Value { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Content/VariantViewModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Content/VariantViewModelBase.cs new file mode 100644 index 0000000000..cd5204582e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Content/VariantViewModelBase.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Content; + +public abstract class VariantViewModelBase +{ + public string? Culture { get; set; } + + public string? Segment { get; set; } + + public string Name { get; set; } = string.Empty; + + public DateTime CreateDate { get; set; } + + public DateTime UpdateDate { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeCleanup.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeCleanup.cs new file mode 100644 index 0000000000..072e2637c6 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeCleanup.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; + +public class ContentTypeCleanup +{ + public bool PreventCleanup { get; init; } + + public int? KeepAllVersionsNewerThanDays { get; init; } + + public int? KeepLatestVersionPerDayForDays { get; init; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeComposition.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeComposition.cs new file mode 100644 index 0000000000..45fe7b463d --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeComposition.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; + +public class ContentTypeComposition +{ + public required Guid Key { get; init; } + + public required ContentTypeCompositionType CompositionType { get; init; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeCompositionType.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeCompositionType.cs new file mode 100644 index 0000000000..ad8479e05b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeCompositionType.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; + +public enum ContentTypeCompositionType +{ + Composition, + Inheritance +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeSort.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeSort.cs new file mode 100644 index 0000000000..4a43e108c0 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeSort.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; + +public class ContentTypeSort +{ + public required Guid Key { get; init; } + + public required int SortOrder { get; init; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeViewModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeViewModelBase.cs new file mode 100644 index 0000000000..bddaf605b7 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/ContentTypeViewModelBase.cs @@ -0,0 +1,34 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; + +public abstract class ContentTypeViewModelBase + where TPropertyType : PropertyTypeViewModelBase + where TPropertyTypeContainer : PropertyTypeContainerViewModelBase +{ + public Guid Key { get; set; } + + public string Alias { get; set; } = string.Empty; + + public string Name { get; set; } = string.Empty; + + public string? Description { get; set; } + + public string Icon { get; set; } = string.Empty; + + public bool AllowedAsRoot { get; set; } + + public bool VariesByCulture { get; set; } + + public bool VariesBySegment { get; set; } + + public bool IsElement { get; set; } + + public IEnumerable Properties { get; set; } = Array.Empty(); + + public IEnumerable Containers { get; set; } = Array.Empty(); + + public IEnumerable AllowedContentTypes { get; set; } = Array.Empty(); + + public IEnumerable Compositions { get; set; } = Array.Empty(); + + public ContentTypeCleanup Cleanup { get; set; } = new(); +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeAppearance.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeAppearance.cs new file mode 100644 index 0000000000..4127e54aef --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeAppearance.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; + +public class PropertyTypeAppearance +{ + public bool LabelOnTop { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeContainerViewModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeContainerViewModelBase.cs new file mode 100644 index 0000000000..049c959c0c --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeContainerViewModelBase.cs @@ -0,0 +1,15 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; + +public abstract class PropertyTypeContainerViewModelBase +{ + public Guid Key { get; set; } + + public Guid? ParentKey { get; set; } + + public string? Name { get; set; } + + // NOTE: This needs to be a string because it can be anything in the future (= not necessarily limited to "tab" or "group") + public string Type { get; set; } = string.Empty; + + public int SortOrder { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeValidation.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeValidation.cs new file mode 100644 index 0000000000..2b4d6e536e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeValidation.cs @@ -0,0 +1,12 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; + +public class PropertyTypeValidation +{ + public bool Mandatory { get; set; } + + public string? MandatoryMessage { get; set; } + + public string? RegEx { get; set; } + + public string? RegExMessage { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeViewModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeViewModelBase.cs new file mode 100644 index 0000000000..51c50e1632 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/ContentType/PropertyTypeViewModelBase.cs @@ -0,0 +1,24 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.ContentType; + +public abstract class PropertyTypeViewModelBase +{ + public Guid Key { get; set; } + + public Guid? ContainerKey { get; set; } + + public string Alias { get; set; } = string.Empty; + + public string Name { get; set; } = string.Empty; + + public string? Description { get; set; } + + public Guid DataTypeKey { get; set; } + + public bool VariesByCulture { get; set; } + + public bool VariesBySegment { get; set; } + + public PropertyTypeValidation Validation { get; set; } = new(); + + public PropertyTypeAppearance Appearance { get; set; } = new(); +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeViewModel.cs index 77a0a8df25..25d094df36 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeViewModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeViewModel.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.DataType; -public class DataTypeViewModel : DataTypeModelBase +public class DataTypeViewModel : DataTypeModelBase, INamedEntityViewModel { public Guid Key { get; set; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemViewModel.cs index a7ee7e5b32..0b795c1fc2 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemViewModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Dictionary/DictionaryItemViewModel.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Dictionary; -public class DictionaryItemViewModel : DictionaryItemModelBase +public class DictionaryItemViewModel : DictionaryItemModelBase, INamedEntityViewModel { public Guid Key { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentValueViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentValueViewModel.cs new file mode 100644 index 0000000000..c8211460cb --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentValueViewModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.Content; + +namespace Umbraco.Cms.Api.Management.ViewModels.Document; + +public class DocumentValueViewModel : ValueViewModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentVariantViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentVariantViewModel.cs new file mode 100644 index 0000000000..501778f03c --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentVariantViewModel.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Api.Management.ViewModels.Content; + +namespace Umbraco.Cms.Api.Management.ViewModels.Document; + +public class DocumentVariantViewModel : VariantViewModelBase +{ + public ContentState State { get; set; } + + public DateTime? PublishDate { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentViewModel.cs new file mode 100644 index 0000000000..a036205959 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/DocumentViewModel.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Api.Management.ViewModels.Content; + +namespace Umbraco.Cms.Api.Management.ViewModels.Document; + +public class DocumentViewModel : ContentViewModelBase +{ + public IEnumerable Urls { get; set; } = Array.Empty(); + + public Guid? TemplateKey { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeContainerViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeContainerViewModel.cs new file mode 100644 index 0000000000..1e51dd63c9 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeContainerViewModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.ContentType; + +namespace Umbraco.Cms.Api.Management.ViewModels.DocumentType; + +public class DocumentTypePropertyTypeContainerViewModel : PropertyTypeContainerViewModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeViewModel.cs new file mode 100644 index 0000000000..b48a85dac6 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypePropertyTypeViewModel.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Api.Management.ViewModels.ContentType; + +namespace Umbraco.Cms.Api.Management.ViewModels.DocumentType; + +public class DocumentTypePropertyTypeViewModel : PropertyTypeViewModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypeViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypeViewModel.cs new file mode 100644 index 0000000000..a89ba3d597 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/DocumentType/DocumentTypeViewModel.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Api.Management.ViewModels.ContentType; + +namespace Umbraco.Cms.Api.Management.ViewModels.DocumentType; + +public class DocumentTypeViewModel : ContentTypeViewModelBase +{ + public IEnumerable AllowedTemplateKeys { get; set; } = Array.Empty(); + + public Guid? DefaultTemplateKey { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/FolderViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Folder/FolderViewModel.cs index f76bf1493f..777ca988fd 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Folder/FolderViewModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Folder/FolderViewModel.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Folder; -public class FolderViewModel : FolderModelBase +public class FolderViewModel : FolderModelBase, INamedEntityViewModel { public Guid Key { get; set; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/INamedEntityViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/INamedEntityViewModel.cs new file mode 100644 index 0000000000..1da1a24c0e --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/INamedEntityViewModel.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Cms.Api.Management.ViewModels; + +public interface INamedEntityViewModel +{ + Guid Key { get; } + + string Name { get;} +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageCreateModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageCreateModel.cs new file mode 100644 index 0000000000..e4ece8fe4f --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageCreateModel.cs @@ -0,0 +1,5 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Package; + +public class PackageCreateModel : PackageModelBase +{ +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageDefinitionViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageDefinitionViewModel.cs new file mode 100644 index 0000000000..7e51bd801b --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageDefinitionViewModel.cs @@ -0,0 +1,17 @@ +using System.ComponentModel; + +namespace Umbraco.Cms.Api.Management.ViewModels.Package; + +public class PackageDefinitionViewModel : PackageModelBase +{ + /// + /// Gets or sets the key. + /// + public Guid Key { get; set; } + + /// + /// Gets or sets the full path to the package's XML file. + /// + [ReadOnly(true)] + public required string PackagePath { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageMigrationStatusViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageMigrationStatusViewModel.cs new file mode 100644 index 0000000000..5b4c7d8ece --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageMigrationStatusViewModel.cs @@ -0,0 +1,14 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Package; + +public class PackageMigrationStatusViewModel +{ + /// + /// Gets or sets the name of the package. + /// + public required string PackageName { get; set; } + + /// + /// Gets or sets a value indicating whether the package has any pending migrations to run. + /// + public bool HasPendingMigrations { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageModelBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageModelBase.cs new file mode 100644 index 0000000000..c3c1fa9460 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageModelBase.cs @@ -0,0 +1,74 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.Package; + +public class PackageModelBase +{ + /// + /// Gets or sets the name. + /// + public required string Name { get; set; } + + /// + /// Gets or sets the id of the selected content node. + /// + public string? ContentNodeId { get; set; } + + /// + /// Gets or sets a value indicating whether to load all child nodes of the selected content node. + /// + public bool ContentLoadChildNodes { get; set; } + + /// + /// Gets or sets the list of media keys for the selected media items. + /// + public IList MediaKeys { get; set; } = new List(); + + /// + /// Gets or sets a value indicating whether to load all child nodes of the selected media items. + /// + public bool MediaLoadChildNodes { get; set; } + + /// + /// Gets or sets the list of ids for the selected document types. + /// + public IList DocumentTypes { get; set; } = new List(); + + /// + /// Gets or sets the list of ids for the selected media types. + /// + public IList MediaTypes { get; set; } = new List(); + + /// + /// Gets or sets the list of ids for the selected data types. + /// + public IList DataTypes { get; set; } = new List(); + + /// + /// Gets or sets the list of ids for the selected templates. + /// + public IList Templates { get; set; } = new List(); + + /// + /// Gets or sets the list of relative paths for the selected partial views. + /// + public IList PartialViews { get; set; } = new List(); + + /// + /// Gets or sets the list of names for the selected stylesheets. + /// + public IList Stylesheets { get; set; } = new List(); + + /// + /// Gets or sets the list of names for the selected scripts. + /// + public IList Scripts { get; set; } = new List(); + + /// + /// Gets or sets the list of ids for the selected languages. + /// + public IList Languages { get; set; } = new List(); + + /// + /// Gets or sets the list of ids for the selected dictionary items. + /// + public IList DictionaryItems { get; set; } = new List(); +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageUpdateModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageUpdateModel.cs new file mode 100644 index 0000000000..548f22fd8c --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Package/PackageUpdateModel.cs @@ -0,0 +1,12 @@ +using System.ComponentModel; + +namespace Umbraco.Cms.Api.Management.ViewModels.Package; + +public class PackageUpdateModel : PackageModelBase +{ + /// + /// Gets or sets the full path to the package's XML file. + /// + [ReadOnly(true)] + public required string PackagePath { get; set; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/RecycleBin/RecycleBinItemViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/RecycleBin/RecycleBinItemViewModel.cs index d3d25622f0..aa7ab3cd46 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/RecycleBin/RecycleBinItemViewModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/RecycleBin/RecycleBinItemViewModel.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.RecycleBin; -public class RecycleBinItemViewModel +public class RecycleBinItemViewModel : INamedEntityViewModel { public Guid Key { get; set; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Template/TemplateViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Template/TemplateViewModel.cs index 8fe8fc7ddd..e8958df583 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Template/TemplateViewModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Template/TemplateViewModel.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Template; -public class TemplateViewModel : TemplateModelBase +public class TemplateViewModel : TemplateModelBase, INamedEntityViewModel { public Guid Key { get; set; } } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/RelationItemViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/RelationItemViewModel.cs index f27c81f3eb..572da9d9ea 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/RelationItemViewModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/TrackedReferences/RelationItemViewModel.cs @@ -8,6 +8,8 @@ public class RelationItemViewModel public string? NodeType { get; set; } + public bool? NodePublished { get; set; } + public string? ContentTypeIcon { get; set; } public string? ContentTypeAlias { get; set; } @@ -19,4 +21,5 @@ public class RelationItemViewModel public bool RelationTypeIsBidirectional { get; set; } public bool RelationTypeIsDependency { get; set; } + } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemViewModel.cs index a163a4f9a7..008bb81c5c 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemViewModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/EntityTreeItemViewModel.cs @@ -1,6 +1,6 @@ namespace Umbraco.Cms.Api.Management.ViewModels.Tree; -public class EntityTreeItemViewModel : TreeItemViewModel +public class EntityTreeItemViewModel : TreeItemViewModel, INamedEntityViewModel { public Guid Key { get; set; } diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupBase.cs b/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupBase.cs new file mode 100644 index 0000000000..994ef00abf --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupBase.cs @@ -0,0 +1,58 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.UserGroups; + +/// +/// +/// Base class for front-end representation of a User Group. +/// +/// +/// Contains all the properties shared between Save, Update, Representation, etc... +/// +/// +public class UserGroupBase +{ + /// + /// The name of the user groups + /// + public required string Name { get; init; } + + /// + /// The Icon for the user group + /// + public string? Icon { get; init; } + + /// + /// The sections that the user group has access to + /// + public required IEnumerable Sections { get; init; } + + /// + /// The languages that the user group has access to + /// + public required IEnumerable Languages { get; init; } + + /// + /// Flag indicating if the user group gives access to all languages, regardless of . + /// + public required bool HasAccessToAllLanguages { get; init; } + + /// + /// The key of the document that should act as root node for the user group + /// + /// This can be overwritten by a different user group if a user is a member of multiple groups + /// + /// + public Guid? DocumentStartNodeKey { get; init; } + + /// + /// The Key of the media that should act as root node for the user group + /// + /// This can be overwritten by a different user group if a user is a member of multiple groups + /// + /// + public Guid? MediaStartNodeKey { get; init; } + + /// + /// Ad-hoc list of permissions provided, and maintained by the front-end. The server has no concept of what these mean. + /// + public required ISet Permissions { get; init; } +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupSaveModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupSaveModel.cs new file mode 100644 index 0000000000..d42a2c2faf --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupSaveModel.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.UserGroups; + +public class UserGroupSaveModel : UserGroupBase +{ + +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupUpdateModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupUpdateModel.cs new file mode 100644 index 0000000000..fa08b60ec9 --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupUpdateModel.cs @@ -0,0 +1,6 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.UserGroups; + +public class UserGroupUpdateModel : UserGroupBase +{ + +} diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupViewModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupViewModel.cs new file mode 100644 index 0000000000..9b6212f7ef --- /dev/null +++ b/src/Umbraco.Cms.Api.Management/ViewModels/UserGroups/UserGroupViewModel.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Cms.Api.Management.ViewModels.UserGroups; + +public class UserGroupViewModel : UserGroupBase, INamedEntityViewModel +{ + /// + /// The key identifier for the user group. + /// + public required Guid Key { get; init; } + +} diff --git a/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj b/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj index d082dff945..5a8dd128a5 100644 --- a/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj +++ b/src/Umbraco.Cms.StaticAssets/Umbraco.Cms.StaticAssets.csproj @@ -16,7 +16,9 @@ - $(ProjectDir)wwwroot\umbraco + $(ProjectDir)wwwroot\umbraco + $(BasePath)\lib + $(BasePath)\backoffice @@ -24,9 +26,19 @@ + + + + + + + + + + @@ -36,12 +48,26 @@ + + + + + + + - + - + + + + + + + + diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml index 51a9d3d9fa..5662c164a6 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Default.cshtml @@ -20,8 +20,10 @@ @inject IProfilerHtml profilerHtml @inject IIconService IconService @inject IBackOfficeExternalLoginProviders externalLogins +@inject IWebProfilerService WebProfilerService @{ - bool.TryParse(Context.Request.Query["umbDebug"], out bool isDebug); + var webProfilingStatus = await WebProfilerService.GetStatus(); + var isDebug = (webProfilingStatus.Success && webProfilingStatus.Result) ? true : false; var backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment); } diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/Maintenance.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/Maintenance.cshtml index 46739cdef7..94de5f3c52 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/Maintenance.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoWebsite/Maintenance.cshtml @@ -17,7 +17,7 @@ Website is Under Maintainance - +