Management API: Data type or property is used endpoints (#14078)

* Added is-used endpoints

* Updated CompatibilitySuppressions.xml

* Minor cleanup

* Fix issue where only document types was supported. Now also media and member types is supported

* CompatibilitySuppressions.xml

---------

Co-authored-by: Nikolaj <nikolajlauridsen@protonmail.ch>
This commit is contained in:
Bjarke Berg
2023-04-13 14:58:16 +02:00
committed by GitHub
parent 7be31a5305
commit f26e5d12a0
16 changed files with 304 additions and 4 deletions

View File

@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.ViewModels.DataType;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Mapping;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.DataType;
public class IsUsedDataTypeController : DataTypeControllerBase
{
private readonly IDataTypeUsageService _dataTypeUsageService;
public IsUsedDataTypeController(IDataTypeUsageService dataTypeUsageService)
{
_dataTypeUsageService = dataTypeUsageService;
}
[HttpGet("{id:guid}/is-used")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> IsUsed(Guid id)
{
Attempt<bool, DataTypeOperationStatus> result = await _dataTypeUsageService.HasSavedValuesAsync(id);
return result.Success
? Ok(result.Result)
: DataTypeOperationStatusResult(result.Status);
}
}

View File

@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.PropertyType;
public class IsUsedPropertyTypeController : PropertyTypeControllerBase
{
private readonly IPropertyTypeUsageService _propertyTypeUsageService;
public IsUsedPropertyTypeController(IPropertyTypeUsageService propertyTypeUsageService)
{
_propertyTypeUsageService = propertyTypeUsageService;
}
[HttpGet("is-used")]
[MapToApiVersion("1.0")]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(PagedViewModel<bool>), StatusCodes.Status200OK)]
public async Task<IActionResult> Get(Guid contentTypeId, string propertyAlias)
{
Attempt<bool, PropertyTypeOperationStatus> result = await _propertyTypeUsageService.HasSavedPropertyValuesAsync(contentTypeId, propertyAlias);
return result.Success
? Ok(result.Result)
: PropertyTypeOperationStatusResult(result.Status);
}
}

View File

@@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Api.Management.Controllers.PropertyType;
[ApiController]
[VersionedApiBackOfficeRoute("property-type")]
[ApiExplorerSettings(GroupName = "Property Type")]
[ApiVersion("1.0")]
public abstract class PropertyTypeControllerBase : ManagementApiControllerBase
{
protected IActionResult PropertyTypeOperationStatusResult(PropertyTypeOperationStatus status) =>
status switch
{
PropertyTypeOperationStatus.ContentTypeNotFound => NotFound("The content type was not found."),
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown data type operation status")
};
}

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
@@ -868,6 +869,27 @@
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Persistence.Repositories.IDataTypeUsageRepository.HasSavedValuesAsync(System.Guid)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Persistence.Repositories.IPropertyTypeUsageRepository.ContentTypeExistAsync(System.Guid)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Persistence.Repositories.IPropertyTypeUsageRepository.HasSavedPropertyValuesAsync(System.Guid,System.String)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Persistence.Repositories.ITrackedReferencesRepository.GetPagedDescendantsInReferences(System.Guid,System.Int64,System.Int64,System.Boolean,System.Int64@)</Target>
@@ -1036,6 +1058,13 @@
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Services.IDataTypeUsageService.HasSavedValuesAsync(System.Guid)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Services.IDomainService.GetAllAsync(System.Boolean)</Target>
@@ -1169,6 +1198,13 @@
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Services.IPropertyTypeUsageService.HasSavedPropertyValuesAsync(System.Guid,System.String)</Target>
<Left>lib/net7.0/Umbraco.Core.dll</Left>
<Right>lib/net7.0/Umbraco.Core.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Umbraco.Cms.Core.Services.IRelationService.CreateAsync(Umbraco.Cms.Core.Models.IRelationType,System.Guid)</Target>

View File

@@ -2,5 +2,7 @@ namespace Umbraco.Cms.Core.Persistence.Repositories;
public interface IDataTypeUsageRepository
{
[Obsolete("Please use HasSavedValuesAsync. Scheduled for removable in Umbraco 15.")]
bool HasSavedValues(int dataTypeId);
Task<bool> HasSavedValuesAsync(Guid dataTypeKey);
}

View File

@@ -2,5 +2,8 @@ namespace Umbraco.Cms.Core.Persistence.Repositories;
public interface IPropertyTypeUsageRepository
{
[Obsolete("Please use HasSavedPropertyValuesAsync. Scheduled for removable in Umbraco 15.")]
bool HasSavedPropertyValues(string propertyTypeAlias);
Task<bool> HasSavedPropertyValuesAsync(Guid contentTypeKey, string propertyAlias);
Task<bool> ContentTypeExistAsync(Guid contentTypeKey);
}

View File

@@ -1,25 +1,59 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Core.Services;
public class DataTypeUsageService : IDataTypeUsageService
{
private readonly IDataTypeUsageRepository _dataTypeUsageRepository;
private readonly IDataTypeService _dataTypeService;
private readonly ICoreScopeProvider _scopeProvider;
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
public DataTypeUsageService(
IDataTypeUsageRepository dataTypeUsageRepository,
ICoreScopeProvider scopeProvider)
: this(dataTypeUsageRepository, StaticServiceProvider.Instance.GetRequiredService<IDataTypeService>(), scopeProvider)
{
}
public DataTypeUsageService(
IDataTypeUsageRepository dataTypeUsageRepository,
IDataTypeService dataTypeService,
ICoreScopeProvider scopeProvider)
{
_dataTypeUsageRepository = dataTypeUsageRepository;
_dataTypeService = dataTypeService;
_scopeProvider = scopeProvider;
}
/// <inheritdoc/>
[Obsolete("Please use HasSavedValuesAsync. Scheduled for removable in Umbraco 15.")]
public bool HasSavedValues(int dataTypeId)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
return _dataTypeUsageRepository.HasSavedValues(dataTypeId);
}
/// <inheritdoc/>
public async Task<Attempt<bool, DataTypeOperationStatus>> HasSavedValuesAsync(Guid dataTypeKey)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
IDataType? dataType = await _dataTypeService.GetAsync(dataTypeKey);
if (dataType is null)
{
return Attempt.FailWithStatus(DataTypeOperationStatus.NotFound, false);
}
var hasSavedValues = await _dataTypeUsageRepository.HasSavedValuesAsync(dataTypeKey);
return Attempt.SucceedWithStatus(DataTypeOperationStatus.Success, hasSavedValues);
}
}

View File

@@ -1,11 +1,17 @@
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Core.Services;
public interface IDataTypeUsageService
{
[Obsolete("Please use HasSavedValuesAsync. Scheduled for removable in Umbraco 15.")]
bool HasSavedValues(int dataTypeId);
/// <summary>
/// Checks if there are any saved property values using a given data type.
/// </summary>
/// <param name="dataTypeId">The ID of the data type to check.</param>
/// <returns>True if there are any property values using the data type, otherwise false.</returns>
bool HasSavedValues(int dataTypeId);
/// <param name="dataTypeKey">The key of the data type to check.</param>
/// <returns>An attempt with status and result if there are any property values using the data type, otherwise false.</returns>
Task<Attempt<bool, DataTypeOperationStatus>> HasSavedValuesAsync(Guid dataTypeKey);
}

View File

@@ -1,3 +1,5 @@
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Core.Services;
public interface IPropertyTypeUsageService
@@ -7,5 +9,14 @@ public interface IPropertyTypeUsageService
/// </summary>
/// <param name="propertyTypeAlias">The alias of the property type to check.</param>
/// <returns>True if the property type has any property values, otherwise false.</returns>
[Obsolete("Please use HasSavedPropertyValuesAsync. Scheduled for removable in Umbraco 15.")]
bool HasSavedPropertyValues(string propertyTypeAlias);
/// <summary>
/// Checks if a property type has any saved property values associated with it.
/// </summary>
/// <param name="contentTypeKey">The key of the content type to check.</param>
/// <param name="propertyAlias">The alias of the property to check.</param>
/// <returns>An attempt with status and result if the property type has any property values, otherwise false.</returns>
Task<Attempt<bool, PropertyTypeOperationStatus>> HasSavedPropertyValuesAsync(Guid contentTypeKey, string propertyAlias);
}

View File

@@ -0,0 +1,7 @@
namespace Umbraco.Cms.Core.Services.OperationStatus;
public enum PropertyTypeOperationStatus
{
Success,
ContentTypeNotFound,
}

View File

@@ -1,25 +1,59 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Core.Services;
public class PropertyTypeUsageService : IPropertyTypeUsageService
{
private readonly IPropertyTypeUsageRepository _propertyTypeUsageRepository;
private readonly IContentTypeService _contentTypeService;
private readonly ICoreScopeProvider _scopeProvider;
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 15.")]
public PropertyTypeUsageService(
IPropertyTypeUsageRepository propertyTypeUsageRepository,
ICoreScopeProvider scopeProvider): this(propertyTypeUsageRepository, StaticServiceProvider.Instance.GetRequiredService<IContentTypeService>(), scopeProvider)
{
}
public PropertyTypeUsageService(
IPropertyTypeUsageRepository propertyTypeUsageRepository,
IContentTypeService contentTypeService,
ICoreScopeProvider scopeProvider)
{
_propertyTypeUsageRepository = propertyTypeUsageRepository;
_contentTypeService = contentTypeService;
_scopeProvider = scopeProvider;
}
/// <inheritdoc/>
[Obsolete("Please use HasSavedPropertyValuesAsync. Scheduled for removable in Umbraco 15.")]
public bool HasSavedPropertyValues(string propertyTypeAlias)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
return _propertyTypeUsageRepository.HasSavedPropertyValues(propertyTypeAlias);
}
/// <inheritdoc/>
public async Task<Attempt<bool, PropertyTypeOperationStatus>> HasSavedPropertyValuesAsync(Guid contentTypeKey, string propertyAlias)
{
using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true);
var contentTypeExists = await _propertyTypeUsageRepository.ContentTypeExistAsync(contentTypeKey);
if (contentTypeExists is false)
{
return Attempt.FailWithStatus(PropertyTypeOperationStatus.ContentTypeNotFound, false);
}
var hasSavedPropertyValues = await _propertyTypeUsageRepository.HasSavedPropertyValuesAsync(contentTypeKey, propertyAlias);
return Attempt.SucceedWithStatus(PropertyTypeOperationStatus.Success, hasSavedPropertyValues);
}
}

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>

View File

@@ -15,6 +15,7 @@ public class DataTypeUsageRepository : IDataTypeUsageRepository
_scopeAccessor = scopeAccessor;
}
[Obsolete("Please use HasSavedValuesAsync. Scheduled for removable in Umbraco 15.")]
public bool HasSavedValues(int dataTypeId)
{
IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database;
@@ -36,4 +37,28 @@ public class DataTypeUsageRepository : IDataTypeUsageRepository
return database.ExecuteScalar<bool>(hasValueQuery);
}
public async Task<bool> HasSavedValuesAsync(Guid dataTypeKey)
{
IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database;
if (database is null)
{
throw new InvalidOperationException("A scope is required to query the database");
}
Sql<ISqlContext> selectQuery = database.SqlContext.Sql()
.SelectAll()
.From<PropertyTypeDto>("pt")
.InnerJoin<PropertyDataDto>("pd")
.On<PropertyDataDto, PropertyTypeDto>((left, right) => left.PropertyTypeId == right.Id, "pd", "pt")
.InnerJoin<NodeDto>("n")
.On<PropertyTypeDto, NodeDto>((pt, n) => pt.DataTypeId == n.NodeId, "pt", "n")
.Where<NodeDto>(n => n.UniqueId == dataTypeKey, "n");
Sql<ISqlContext> hasValueQuery = database.SqlContext.Sql()
.SelectAnyIfExists(selectQuery);
return await Task.FromResult(database.ExecuteScalar<bool>(hasValueQuery));
}
}

View File

@@ -1,5 +1,6 @@
using System;
using NPoco;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Cms.Infrastructure.Scoping;
@@ -9,6 +10,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
internal class PropertyTypeUsageRepository : IPropertyTypeUsageRepository
{
private static readonly Guid?[] NodeObjectTypes = new Guid?[]
{
Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.MediaType, Constants.ObjectTypes.MemberType,
};
private readonly IScopeAccessor _scopeAccessor;
public PropertyTypeUsageRepository(IScopeAccessor scopeAccessor)
@@ -16,6 +22,7 @@ internal class PropertyTypeUsageRepository : IPropertyTypeUsageRepository
_scopeAccessor = scopeAccessor;
}
[Obsolete("Please use HasSavedPropertyValuesAsync. Scheduled for removable in Umbraco 15.")]
public bool HasSavedPropertyValues(string propertyTypeAlias)
{
IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database;
@@ -37,4 +44,52 @@ internal class PropertyTypeUsageRepository : IPropertyTypeUsageRepository
return database.ExecuteScalar<bool>(hasValuesQuery);
}
public async Task<bool> HasSavedPropertyValuesAsync(Guid contentTypeKey, string propertyAlias)
{
IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database;
if (database is null)
{
throw new InvalidOperationException("A scope is required to query the database");
}
Sql<ISqlContext> selectQuery = database.SqlContext.Sql()
.SelectAll()
.From<PropertyTypeDto>("pt")
.InnerJoin<PropertyDataDto>("pd")
.On<PropertyDataDto, PropertyTypeDto>((pd, pt) => pd.PropertyTypeId == pt.Id, "pd", "pt")
.InnerJoin<NodeDto>("n")
.On<PropertyTypeDto, NodeDto>((pt, n) => pt.ContentTypeId == n.NodeId, "pt", "n")
.Where<PropertyTypeDto>(pt => pt.Alias == propertyAlias, "pt")
.Where<NodeDto>(n => n.UniqueId == contentTypeKey, "n");
Sql<ISqlContext> hasValuesQuery = database.SqlContext.Sql()
.SelectAnyIfExists(selectQuery);
return database.ExecuteScalar<bool>(hasValuesQuery);
}
public async Task<bool> ContentTypeExistAsync(Guid contentTypeKey)
{
IUmbracoDatabase? database = _scopeAccessor.AmbientScope?.Database;
if (database is null)
{
throw new InvalidOperationException("A scope is required to query the database");
}
Sql<ISqlContext> selectQuery = database.SqlContext.Sql()
.SelectAll()
.From<NodeDto>("n")
.Where<NodeDto>(n => n.UniqueId == contentTypeKey, "n")
.Where<NodeDto>(n => NodeObjectTypes.Contains(n.NodeObjectType), "n");
Sql<ISqlContext> hasValuesQuery = database.SqlContext.Sql()
.SelectAnyIfExists(selectQuery);
return database.ExecuteScalar<bool>(hasValuesQuery);
}
}

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>