Domains and hosts API (#13963)
* API for domains and hostnames incl. unit tests * Update Open API json * Update other unit tests to use new domain service methods where applicable * Fix merge + update models to new naming scheme * Handle attempts to add the same domain twice + unit tests for duplicate domain handling * Review fixes
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
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.Controllers.Document;
|
||||
|
||||
public class DomainsController : DocumentControllerBase
|
||||
{
|
||||
private readonly IDomainService _domainService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public DomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_domainService = domainService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[HttpGet("{key:guid}/domains")]
|
||||
public async Task<IActionResult> DomainsAsync(Guid key)
|
||||
{
|
||||
IDomain[] assignedDomains = (await _domainService.GetAssignedDomainsAsync(key, true))
|
||||
.OrderBy(d => d.SortOrder)
|
||||
.ToArray();
|
||||
|
||||
DomainsResponseModel responseModel = _umbracoMapper.Map<DomainsResponseModel>(assignedDomains)!;
|
||||
return Ok(responseModel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.Builders;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Document;
|
||||
|
||||
public class UpdateDomainsController : DocumentControllerBase
|
||||
{
|
||||
private readonly IDomainService _domainService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public UpdateDomainsController(IDomainService domainService, IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_domainService = domainService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[HttpPut("{key:guid}/domains")]
|
||||
public async Task<IActionResult> UpdateDomainsAsync(Guid key, UpdateDomainsRequestModel updateModel)
|
||||
{
|
||||
DomainsUpdateModel domainsUpdateModel = _umbracoMapper.Map<DomainsUpdateModel>(updateModel)!;
|
||||
|
||||
Attempt<IEnumerable<IDomain>, DomainOperationStatus> result = await _domainService.UpdateDomainsAsync(key, domainsUpdateModel);
|
||||
|
||||
return result.Status switch
|
||||
{
|
||||
DomainOperationStatus.Success => Ok(),
|
||||
DomainOperationStatus.CancelledByNotification => BadRequest(new ProblemDetailsBuilder()
|
||||
.WithTitle("Cancelled by notification")
|
||||
.WithDetail("A notification handler prevented the domain update operation.")
|
||||
.Build()),
|
||||
DomainOperationStatus.ContentNotFound => NotFound("The targeted content item was not found."),
|
||||
DomainOperationStatus.LanguageNotFound => BadRequest(new ProblemDetailsBuilder()
|
||||
.WithTitle("Invalid language specified")
|
||||
.WithDetail("One or more of the specified language ISO codes could not be found.")
|
||||
.Build()),
|
||||
DomainOperationStatus.DuplicateDomainName => Conflict(new ProblemDetailsBuilder()
|
||||
.WithTitle("Duplicate domain name detected")
|
||||
.WithDetail("One or more of the specified domain names were duplicates, possibly of assignments to other content items.")
|
||||
.Build()),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, "Unknown domain update operation status")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,9 @@ internal static class DocumentBuilderExtensions
|
||||
builder.Services.AddTransient<IContentUrlFactory, ContentUrlFactory>();
|
||||
builder.Services.AddTransient<IDocumentEditingPresentationFactory, DocumentEditingPresentationFactory>();
|
||||
|
||||
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>().Add<DocumentMapDefinition>();
|
||||
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
|
||||
.Add<DocumentMapDefinition>()
|
||||
.Add<DomainMapDefinition>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using CoreDomainModel = Umbraco.Cms.Core.Models.ContentEditing.DomainModel;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Mapping.Document;
|
||||
|
||||
public class DomainMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(IUmbracoMapper mapper)
|
||||
{
|
||||
mapper.Define<IEnumerable<IDomain>, DomainsResponseModel>((_, _) => new DomainsResponseModel { Domains = Enumerable.Empty<DomainPresentationModel>() }, Map);
|
||||
mapper.Define<UpdateDomainsRequestModel, DomainsUpdateModel>((_, _) => new DomainsUpdateModel { Domains = Enumerable.Empty<CoreDomainModel>() }, Map);
|
||||
}
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private void Map(IEnumerable<IDomain> source, DomainsResponseModel target, MapperContext context)
|
||||
{
|
||||
IDomain[] sourceAsArray = source.ToArray();
|
||||
IDomain[] wildcardsDomains = sourceAsArray.Where(d => d.IsWildcard).ToArray();
|
||||
|
||||
target.DefaultIsoCode = wildcardsDomains.FirstOrDefault()?.LanguageIsoCode;
|
||||
target.Domains = sourceAsArray.Except(wildcardsDomains).Select(domain => new DomainPresentationModel
|
||||
{
|
||||
DomainName = domain.DomainName,
|
||||
IsoCode = domain.LanguageIsoCode ?? string.Empty
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private void Map(UpdateDomainsRequestModel source, DomainsUpdateModel target, MapperContext context)
|
||||
{
|
||||
target.DefaultIsoCode = source.DefaultIsoCode;
|
||||
target.Domains = source.Domains.Select(domain => new Core.Models.ContentEditing.DomainModel
|
||||
{
|
||||
DomainName = domain.DomainName,
|
||||
IsoCode = domain.IsoCode
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -1745,6 +1745,65 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/document/{key}/domains": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Document"
|
||||
],
|
||||
"operationId": "GetDocumentByKeyDomains",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "key",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"tags": [
|
||||
"Document"
|
||||
],
|
||||
"operationId": "PutDocumentByKeyDomains",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "key",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/UpdateDomainsRequestModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/recycle-bin/document/children": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -7775,6 +7834,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DomainPresentationModel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"domainName": {
|
||||
"type": "string"
|
||||
},
|
||||
"isoCode": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"DomainsPresentationModelBaseModel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"defaultIsoCode": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"domains": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DomainPresentationModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"DomainsResponseModel": {
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DomainsPresentationModelBaseModel"
|
||||
}
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"EntityTreeItemResponseModel": {
|
||||
"required": [
|
||||
"$type"
|
||||
@@ -10307,6 +10407,15 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"UpdateDomainsRequestModel": {
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DomainsPresentationModelBaseModel"
|
||||
}
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"UpdateFolderReponseModel": {
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
|
||||
public class DomainPresentationModel
|
||||
{
|
||||
public required string DomainName { get; set; }
|
||||
|
||||
public required string IsoCode { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
|
||||
public abstract class DomainsPresentationModelBase
|
||||
{
|
||||
public string? DefaultIsoCode { get; set; }
|
||||
|
||||
public required IEnumerable<DomainPresentationModel> Domains { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
|
||||
public class DomainsResponseModel : DomainsPresentationModelBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Document;
|
||||
|
||||
public class UpdateDomainsRequestModel : DomainsPresentationModelBase
|
||||
{
|
||||
}
|
||||
8
src/Umbraco.Core/Models/ContentEditing/DomainModel.cs
Normal file
8
src/Umbraco.Core/Models/ContentEditing/DomainModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
public class DomainModel
|
||||
{
|
||||
public required string DomainName { get; set; }
|
||||
|
||||
public required string IsoCode { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
public class DomainsUpdateModel
|
||||
{
|
||||
public string? DefaultIsoCode { get; set; }
|
||||
|
||||
public required IEnumerable<DomainModel> Domains { get; set; }
|
||||
}
|
||||
@@ -12,4 +12,9 @@ public class DomainDeletedNotification : DeletedNotification<IDomain>
|
||||
: base(target, messages)
|
||||
{
|
||||
}
|
||||
|
||||
public DomainDeletedNotification(IEnumerable<IDomain> target, EventMessages messages)
|
||||
: base(target, messages)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,52 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
public class DomainService : RepositoryService, IDomainService
|
||||
{
|
||||
private readonly IDomainRepository _domainRepository;
|
||||
private readonly ILanguageService _languageService;
|
||||
private readonly IContentService _contentService;
|
||||
|
||||
[Obsolete("Please use the constructor that accepts ILanguageService and IContentService. Will be removed in V15.")]
|
||||
public DomainService(
|
||||
ICoreScopeProvider provider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IEventMessagesFactory eventMessagesFactory,
|
||||
IDomainRepository domainRepository)
|
||||
: this(
|
||||
provider,
|
||||
loggerFactory,
|
||||
eventMessagesFactory,
|
||||
domainRepository,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILanguageService>(),
|
||||
StaticServiceProvider.Instance.GetRequiredService<IContentService>())
|
||||
{
|
||||
}
|
||||
|
||||
public DomainService(
|
||||
ICoreScopeProvider provider,
|
||||
ILoggerFactory loggerFactory,
|
||||
IEventMessagesFactory eventMessagesFactory,
|
||||
IDomainRepository domainRepository,
|
||||
ILanguageService languageService,
|
||||
IContentService contentService)
|
||||
: base(provider, loggerFactory, eventMessagesFactory)
|
||||
=> _domainRepository = domainRepository;
|
||||
{
|
||||
_domainRepository = domainRepository;
|
||||
_languageService = languageService;
|
||||
_contentService = contentService;
|
||||
}
|
||||
|
||||
public bool Exists(string domainName)
|
||||
{
|
||||
@@ -27,26 +56,16 @@ public class DomainService : RepositoryService, IDomainService
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete($"Please use {nameof(UpdateDomainsAsync)}. Will be removed in V15")]
|
||||
public Attempt<OperationResult?> Delete(IDomain domain)
|
||||
{
|
||||
EventMessages eventMessages = EventMessagesFactory.Get();
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
var deletingNotification = new DomainDeletingNotification(domain, eventMessages);
|
||||
if (scope.Notifications.PublishCancelable(deletingNotification))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(eventMessages);
|
||||
}
|
||||
var result = DeleteAll(new[] { domain }, scope, eventMessages);
|
||||
scope.Complete();
|
||||
|
||||
_domainRepository.Delete(domain);
|
||||
scope.Complete();
|
||||
|
||||
scope.Notifications.Publish(new DomainDeletedNotification(domain, eventMessages).WithStateFrom(deletingNotification));
|
||||
}
|
||||
|
||||
return OperationResult.Attempt.Succeed(eventMessages);
|
||||
return result ? OperationResult.Attempt.Succeed(eventMessages) : OperationResult.Attempt.Cancel(eventMessages);
|
||||
}
|
||||
|
||||
public IDomain? GetByName(string name)
|
||||
@@ -65,6 +84,7 @@ public class DomainService : RepositoryService, IDomainService
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete($"Please use {nameof(GetAllAsync)}. Will be removed in V15")]
|
||||
public IEnumerable<IDomain> GetAll(bool includeWildcards)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
@@ -73,6 +93,7 @@ public class DomainService : RepositoryService, IDomainService
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete($"Please use {nameof(GetAssignedDomainsAsync)}. Will be removed in V15")]
|
||||
public IEnumerable<IDomain> GetAssignedDomains(int contentId, bool includeWildcards)
|
||||
{
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
@@ -81,28 +102,19 @@ public class DomainService : RepositoryService, IDomainService
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete($"Please use {nameof(UpdateDomainsAsync)}. Will be removed in V15")]
|
||||
public Attempt<OperationResult?> Save(IDomain domainEntity)
|
||||
{
|
||||
EventMessages eventMessages = EventMessagesFactory.Get();
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
|
||||
{
|
||||
var savingNotification = new DomainSavingNotification(domainEntity, eventMessages);
|
||||
if (scope.Notifications.PublishCancelable(savingNotification))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(eventMessages);
|
||||
}
|
||||
var result = SaveAll(new[] { domainEntity }, scope, eventMessages);
|
||||
scope.Complete();
|
||||
|
||||
_domainRepository.Save(domainEntity);
|
||||
scope.Complete();
|
||||
|
||||
scope.Notifications.Publish(new DomainSavedNotification(domainEntity, eventMessages).WithStateFrom(savingNotification));
|
||||
}
|
||||
|
||||
return OperationResult.Attempt.Succeed(eventMessages);
|
||||
return result ? OperationResult.Attempt.Succeed(eventMessages) : OperationResult.Attempt.Cancel(eventMessages);
|
||||
}
|
||||
|
||||
[Obsolete($"Please use {nameof(UpdateDomainsAsync)}. Will be removed in V15")]
|
||||
public Attempt<OperationResult?> Sort(IEnumerable<IDomain> items)
|
||||
{
|
||||
EventMessages eventMessages = EventMessagesFactory.Get();
|
||||
@@ -144,4 +156,205 @@ public class DomainService : RepositoryService, IDomainService
|
||||
|
||||
return OperationResult.Attempt.Succeed(eventMessages);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<IDomain>> GetAssignedDomainsAsync(Guid contentKey, bool includeWildcards)
|
||||
{
|
||||
IContent? content = _contentService.GetById(contentKey);
|
||||
if (content == null)
|
||||
{
|
||||
return await Task.FromResult(Enumerable.Empty<IDomain>());
|
||||
}
|
||||
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
|
||||
return _domainRepository.GetAssignedDomains(content.Id, includeWildcards);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<IDomain>> GetAllAsync(bool includeWildcards)
|
||||
{
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true);
|
||||
return await Task.FromResult(_domainRepository.GetAll(includeWildcards));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Attempt<IEnumerable<IDomain>, DomainOperationStatus>> UpdateDomainsAsync(Guid contentKey, DomainsUpdateModel updateModel)
|
||||
{
|
||||
IContent? content = _contentService.GetById(contentKey);
|
||||
if (content == null)
|
||||
{
|
||||
return Attempt.FailWithStatus(DomainOperationStatus.ContentNotFound, Enumerable.Empty<IDomain>());
|
||||
}
|
||||
|
||||
using ICoreScope scope = ScopeProvider.CreateCoreScope();
|
||||
|
||||
IEnumerable<ILanguage> allLanguages = await _languageService.GetAllAsync();
|
||||
var languageIdByIsoCode = allLanguages.ToDictionary(l => l.IsoCode, l => l.Id);
|
||||
|
||||
// validate language ISO codes
|
||||
if (HasInvalidIsoCode(updateModel, languageIdByIsoCode.Keys))
|
||||
{
|
||||
return Attempt.FailWithStatus(DomainOperationStatus.LanguageNotFound, Enumerable.Empty<IDomain>());
|
||||
}
|
||||
|
||||
// ensure all domain names in the update model are lowercased
|
||||
foreach (DomainModel domainModel in updateModel.Domains)
|
||||
{
|
||||
domainModel.DomainName = domainModel.DomainName.ToLowerInvariant();
|
||||
}
|
||||
|
||||
// make sure we're not attempting to assign duplicate domains
|
||||
if (updateModel.Domains.GroupBy(domain => domain.DomainName).Any(group => group.Count() > 1))
|
||||
{
|
||||
return Attempt.FailWithStatus(DomainOperationStatus.DuplicateDomainName, Enumerable.Empty<IDomain>());
|
||||
}
|
||||
|
||||
// grab all current domain assignments
|
||||
IDomain[] allDomains = (await GetAllAsync(true)).ToArray();
|
||||
|
||||
// validate the domain names in the update model
|
||||
if (HasDomainNameConflicts(content.Id, updateModel, allDomains))
|
||||
{
|
||||
return Attempt.FailWithStatus(DomainOperationStatus.DuplicateDomainName, Enumerable.Empty<IDomain>());
|
||||
}
|
||||
|
||||
// find the domains currently assigned to the content item
|
||||
IDomain[] currentlyAssignedDomains = allDomains.Where(domain => domain.RootContentId == content.Id).ToArray();
|
||||
|
||||
// calculate the new domain assignments
|
||||
IDomain[] newAssignedDomains = CalculateNewAssignedDomains(content.Id, updateModel, currentlyAssignedDomains, languageIdByIsoCode);
|
||||
|
||||
EventMessages eventMessages = EventMessagesFactory.Get();
|
||||
|
||||
scope.WriteLock(Constants.Locks.Domains);
|
||||
|
||||
// delete any obsolete domain assignments
|
||||
if (DeleteAll(currentlyAssignedDomains.Except(newAssignedDomains).ToArray(), scope, eventMessages) == false)
|
||||
{
|
||||
scope.Complete();
|
||||
|
||||
// this is the only error scenario in DeleteAll
|
||||
return Attempt.FailWithStatus(DomainOperationStatus.CancelledByNotification, newAssignedDomains.AsEnumerable());
|
||||
}
|
||||
|
||||
// update all domain assignments (also current ones, in case sort order or ISO code has changed)
|
||||
var result = SaveAll(newAssignedDomains, scope, eventMessages);
|
||||
scope.Complete();
|
||||
|
||||
return result
|
||||
? Attempt.SucceedWithStatus(DomainOperationStatus.Success, newAssignedDomains.AsEnumerable())
|
||||
: Attempt.FailWithStatus(DomainOperationStatus.CancelledByNotification, newAssignedDomains.AsEnumerable());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if any of the ISO codes in the update model are invalid
|
||||
/// </summary>
|
||||
private bool HasInvalidIsoCode(DomainsUpdateModel updateModel, IEnumerable<string> allIsoCodes)
|
||||
=> new[] { updateModel.DefaultIsoCode }
|
||||
.Union(updateModel.Domains.Select(domainModel => domainModel.IsoCode))
|
||||
.WhereNotNull()
|
||||
.Except(allIsoCodes)
|
||||
.Any();
|
||||
|
||||
/// <summary>
|
||||
/// Tests if any of the domain names in the update model are assigned to other content items
|
||||
/// </summary>
|
||||
private bool HasDomainNameConflicts(int contentId, DomainsUpdateModel updateModel, IEnumerable<IDomain> allDomains)
|
||||
{
|
||||
var domainNamesAssignedToOtherContent = allDomains
|
||||
.Where(domain => domain.IsWildcard == false && domain.RootContentId != contentId)
|
||||
.Select(domain => domain.DomainName.ToLowerInvariant())
|
||||
.ToArray();
|
||||
|
||||
return updateModel.Domains.Any(domainModel => domainNamesAssignedToOtherContent.InvariantContains(domainModel.DomainName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the new domain assignment incl. wildcard domains
|
||||
/// </summary>
|
||||
private IDomain[] CalculateNewAssignedDomains(int contentId, DomainsUpdateModel updateModel, IDomain[] currentlyAssignedDomains, IDictionary<string, int> languageIdByIsoCode)
|
||||
{
|
||||
// calculate the assigned domains as they should be after updating (including wildcard domains)
|
||||
var newAssignedDomains = new List<IDomain>();
|
||||
if (updateModel.DefaultIsoCode.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
IDomain defaultDomain = currentlyAssignedDomains.FirstOrDefault(domain => domain.IsWildcard && domain.LanguageIsoCode == updateModel.DefaultIsoCode)
|
||||
?? new UmbracoDomain($"*{contentId}")
|
||||
{
|
||||
LanguageId = languageIdByIsoCode[updateModel.DefaultIsoCode],
|
||||
RootContentId = contentId
|
||||
};
|
||||
|
||||
// wildcard domains should have sort order -1 (lowest possible sort order)
|
||||
defaultDomain.SortOrder = -1;
|
||||
|
||||
newAssignedDomains.Add(defaultDomain);
|
||||
}
|
||||
|
||||
var sortOrder = 0;
|
||||
foreach (DomainModel domainModel in updateModel.Domains)
|
||||
{
|
||||
IDomain assignedDomain = currentlyAssignedDomains.FirstOrDefault(domain => domainModel.DomainName.InvariantEquals(domain.DomainName))
|
||||
?? new UmbracoDomain(domainModel.DomainName)
|
||||
{
|
||||
LanguageId = languageIdByIsoCode[domainModel.IsoCode],
|
||||
RootContentId = contentId
|
||||
};
|
||||
|
||||
assignedDomain.SortOrder = sortOrder++;
|
||||
newAssignedDomains.Add(assignedDomain);
|
||||
}
|
||||
|
||||
return newAssignedDomains.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles deletion of one or more domains incl. notifications
|
||||
/// </summary>
|
||||
private bool DeleteAll(IDomain[] domainsToDelete, ICoreScope scope, EventMessages eventMessages)
|
||||
{
|
||||
if (domainsToDelete.Any() == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var deletingNotification = new DomainDeletingNotification(domainsToDelete, eventMessages);
|
||||
if (scope.Notifications.PublishCancelable(deletingNotification))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (IDomain domainToDelete in domainsToDelete)
|
||||
{
|
||||
_domainRepository.Delete(domainToDelete);
|
||||
}
|
||||
|
||||
scope.Notifications.Publish(new DomainDeletedNotification(domainsToDelete, eventMessages).WithStateFrom(deletingNotification));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles saving of one or more domains incl. notifications
|
||||
/// </summary>
|
||||
private bool SaveAll(IDomain[] domainsToSave, ICoreScope scope, EventMessages eventMessages)
|
||||
{
|
||||
if (domainsToSave.Any() == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var savingNotification = new DomainSavingNotification(domainsToSave, eventMessages);
|
||||
if (scope.Notifications.PublishCancelable(savingNotification))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (IDomain assignedDomain in domainsToSave)
|
||||
{
|
||||
_domainRepository.Save(assignedDomain);
|
||||
}
|
||||
|
||||
scope.Notifications.Publish(new DomainSavedNotification(domainsToSave, eventMessages).WithStateFrom(savingNotification));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
@@ -7,18 +9,43 @@ public interface IDomainService : IService
|
||||
{
|
||||
bool Exists(string domainName);
|
||||
|
||||
[Obsolete($"Please use {nameof(UpdateDomainsAsync)}. Will be removed in V15")]
|
||||
Attempt<OperationResult?> Delete(IDomain domain);
|
||||
|
||||
IDomain? GetByName(string name);
|
||||
|
||||
IDomain? GetById(int id);
|
||||
|
||||
[Obsolete($"Please use {nameof(GetAllAsync)}. Will be removed in V15")]
|
||||
IEnumerable<IDomain> GetAll(bool includeWildcards);
|
||||
|
||||
[Obsolete($"Please use {nameof(GetAssignedDomainsAsync)}. Will be removed in V15")]
|
||||
IEnumerable<IDomain> GetAssignedDomains(int contentId, bool includeWildcards);
|
||||
|
||||
[Obsolete($"Please use {nameof(UpdateDomainsAsync)}. Will be removed in V15")]
|
||||
Attempt<OperationResult?> Save(IDomain domainEntity);
|
||||
|
||||
[Obsolete($"Please use {nameof(UpdateDomainsAsync)}. Will be removed in V15")]
|
||||
Attempt<OperationResult?> Sort(IEnumerable<IDomain> items)
|
||||
=> Attempt.Fail(new OperationResult(OperationResultType.Failed, new EventMessages())); // TODO Remove default implmentation in a future version
|
||||
|
||||
/// <summary>
|
||||
/// Gets all assigned domains for content item.
|
||||
/// </summary>
|
||||
/// <param name="contentKey">The key of the content item.</param>
|
||||
/// <param name="includeWildcards">Whether or not to include wildcard domains.</param>
|
||||
Task<IEnumerable<IDomain>> GetAssignedDomainsAsync(Guid contentKey, bool includeWildcards);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all assigned domains.
|
||||
/// </summary>
|
||||
/// <param name="includeWildcards">Whether or not to include wildcard domains.</param>
|
||||
Task<IEnumerable<IDomain>> GetAllAsync(bool includeWildcards);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the domain assignments for a content item.
|
||||
/// </summary>
|
||||
/// <param name="contentKey">The key of the content item.</param>
|
||||
/// <param name="updateModel">The domain assignments to apply.</param>
|
||||
Task<Attempt<IEnumerable<IDomain>, DomainOperationStatus>> UpdateDomainsAsync(Guid contentKey, DomainsUpdateModel updateModel);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Cms.Core.Services.OperationStatus;
|
||||
|
||||
public enum DomainOperationStatus
|
||||
{
|
||||
Success,
|
||||
CancelledByNotification,
|
||||
ContentNotFound,
|
||||
LanguageNotFound,
|
||||
DuplicateDomainName
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
|
||||
@@ -82,18 +83,28 @@ public class TelemetryProviderTests : UmbracoIntegrationTest
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Domain_Telemetry_Provider_Can_Get_Domains()
|
||||
public async Task Domain_Telemetry_Provider_Can_Get_Domains()
|
||||
{
|
||||
// Arrange
|
||||
DomainService.Save(new UmbracoDomain("danish", "da-DK"));
|
||||
var contentType = ContentTypeBuilder.CreateBasicContentType();
|
||||
ContentTypeService.Save(contentType);
|
||||
|
||||
var content = ContentBuilder.CreateBasicContent(contentType);
|
||||
ContentService.Save(content);
|
||||
|
||||
await DomainService.UpdateDomainsAsync(
|
||||
content.Key,
|
||||
new DomainsUpdateModel
|
||||
{
|
||||
Domains = new[] { new DomainModel { DomainName = "english", IsoCode = "en-US" } }
|
||||
});
|
||||
|
||||
IEnumerable<UsageInformation> result = null;
|
||||
// Act
|
||||
result = DetailedTelemetryProviders.GetInformation();
|
||||
|
||||
IEnumerable<UsageInformation> result = DetailedTelemetryProviders.GetInformation();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, result.First().Data);
|
||||
Assert.AreEqual("DomainCount", result.First().Name);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -13,6 +13,7 @@ using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
@@ -26,6 +27,7 @@ using Umbraco.Cms.Tests.Common.Extensions;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
using Umbraco.Extensions;
|
||||
using Language = Umbraco.Cms.Core.Models.Language;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
|
||||
|
||||
@@ -1945,7 +1947,7 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Empty_RecycleBin_With_Content_That_Has_All_Related_Data()
|
||||
public async Task Can_Empty_RecycleBin_With_Content_That_Has_All_Related_Data()
|
||||
{
|
||||
// Arrange
|
||||
// need to:
|
||||
@@ -1994,9 +1996,13 @@ public class ContentServiceTests : UmbracoIntegrationTestWithContent
|
||||
Assert.IsNotNull(NotificationService.CreateNotification(user, content1, "X"));
|
||||
|
||||
ContentService.SetPermission(content1, 'A', new[] { userGroup.Id });
|
||||
|
||||
Assert.IsTrue(DomainService.Save(new UmbracoDomain("www.test.com", "en-AU") { RootContentId = content1.Id })
|
||||
.Success);
|
||||
var updateDomainResult = await DomainService.UpdateDomainsAsync(
|
||||
content1.Key,
|
||||
new DomainsUpdateModel
|
||||
{
|
||||
Domains = new[] { new DomainModel { DomainName = "www.test.com", IsoCode = "en-US" } }
|
||||
});
|
||||
Assert.IsTrue(updateDomainResult.Success);
|
||||
|
||||
// Act
|
||||
ContentService.MoveToRecycleBin(content1);
|
||||
|
||||
@@ -496,13 +496,23 @@ public class ContentControllerTests : UmbracoTestServerTestBase
|
||||
.Build();
|
||||
|
||||
var enLanguage = await languageService.GetAsync(UsIso);
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var enDomain = new UmbracoDomain("/en") {RootContentId = content.Id, LanguageId = enLanguage.Id};
|
||||
domainService.Save(enDomain);
|
||||
|
||||
var dkLanguage = await languageService.GetAsync(DkIso);
|
||||
var dkDomain = new UmbracoDomain("/dk") {RootContentId = childContent.Id, LanguageId = dkLanguage.Id};
|
||||
domainService.Save(dkDomain);
|
||||
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
|
||||
await domainService.UpdateDomainsAsync(
|
||||
content.Key,
|
||||
new DomainsUpdateModel
|
||||
{
|
||||
Domains = new[] { new DomainModel { DomainName = "/en", IsoCode = enLanguage.IsoCode } }
|
||||
});
|
||||
|
||||
await domainService.UpdateDomainsAsync(
|
||||
childContent.Key,
|
||||
new DomainsUpdateModel
|
||||
{
|
||||
Domains = new[] { new DomainModel { DomainName = "/dk", IsoCode = dkLanguage.IsoCode } }
|
||||
});
|
||||
|
||||
var url = PrepareApiControllerUrl<ContentController>(x => x.PostSave(null));
|
||||
|
||||
@@ -559,8 +569,13 @@ public class ContentControllerTests : UmbracoTestServerTestBase
|
||||
|
||||
var dkLanguage = await languageService.GetAsync(DkIso);
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var dkDomain = new UmbracoDomain("/") {RootContentId = content.Id, LanguageId = dkLanguage.Id};
|
||||
domainService.Save(dkDomain);
|
||||
|
||||
await domainService.UpdateDomainsAsync(
|
||||
content.Key,
|
||||
new DomainsUpdateModel
|
||||
{
|
||||
Domains = new[] { new DomainModel { DomainName = "/", IsoCode = dkLanguage.IsoCode } }
|
||||
});
|
||||
|
||||
var url = PrepareApiControllerUrl<ContentController>(x => x.PostSave(null));
|
||||
|
||||
@@ -631,12 +646,20 @@ public class ContentControllerTests : UmbracoTestServerTestBase
|
||||
var dkLanguage = await languageService.GetAsync(DkIso);
|
||||
var usLanguage = await languageService.GetAsync(UsIso);
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var dkDomain = new UmbracoDomain("/") {RootContentId = rootNode.Id, LanguageId = dkLanguage.Id};
|
||||
|
||||
var usDomain = new UmbracoDomain("/en") {RootContentId = childNode.Id, LanguageId = usLanguage.Id};
|
||||
await domainService.UpdateDomainsAsync(
|
||||
rootNode.Key,
|
||||
new DomainsUpdateModel
|
||||
{
|
||||
Domains = new[] { new DomainModel { DomainName = "/", IsoCode = dkLanguage.IsoCode } }
|
||||
});
|
||||
|
||||
domainService.Save(dkDomain);
|
||||
domainService.Save(usDomain);
|
||||
await domainService.UpdateDomainsAsync(
|
||||
childNode.Key,
|
||||
new DomainsUpdateModel
|
||||
{
|
||||
Domains = new[] { new DomainModel { DomainName = "/en", IsoCode = usLanguage.IsoCode } }
|
||||
});
|
||||
|
||||
var url = PrepareApiControllerUrl<ContentController>(x => x.PostSave(null));
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Packaging;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.UrlAndDomains;
|
||||
|
||||
@@ -74,17 +72,147 @@ public class DomainAndUrlsTests : UmbracoIntegrationTest
|
||||
private readonly TestVariationContextAccessor _variationContextAccessor = new();
|
||||
|
||||
public IContent Root { get; set; }
|
||||
|
||||
public string[] Cultures { get; set; }
|
||||
|
||||
[Test]
|
||||
public async Task Can_Update_Domains_For_All_Cultures()
|
||||
{
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var updateModel = new DomainsUpdateModel
|
||||
{
|
||||
Domains = Cultures.Select(culture => new DomainModel
|
||||
{
|
||||
DomainName = GetDomainUrlFromCultureCode(culture), IsoCode = culture
|
||||
})
|
||||
};
|
||||
|
||||
var result = await domainService.UpdateDomainsAsync(Root.Key, updateModel);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(DomainOperationStatus.Success, result.Status);
|
||||
|
||||
void VerifyDomains(IDomain[] domains)
|
||||
{
|
||||
Assert.AreEqual(3, domains.Length);
|
||||
for (var i = 0; i < domains.Length; i++)
|
||||
{
|
||||
Assert.AreEqual(Cultures[i], domains[i].LanguageIsoCode);
|
||||
Assert.AreEqual(GetDomainUrlFromCultureCode(Cultures[i]), domains[i].DomainName);
|
||||
}
|
||||
}
|
||||
|
||||
VerifyDomains(result.Result.ToArray());
|
||||
|
||||
// re-get and verify again
|
||||
var domains = await domainService.GetAssignedDomainsAsync(Root.Key, true);
|
||||
VerifyDomains(domains.ToArray());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Having_three_cultures_and_set_domain_on_all_of_them()
|
||||
public async Task Can_Sort_Domains()
|
||||
{
|
||||
foreach (var culture in Cultures)
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var reversedCultures = Cultures.Reverse().ToArray();
|
||||
var updateModel = new DomainsUpdateModel
|
||||
{
|
||||
SetDomainOnContent(Root, culture, GetDomainUrlFromCultureCode(culture));
|
||||
Domains = reversedCultures.Select(culture => new DomainModel
|
||||
{
|
||||
DomainName = GetDomainUrlFromCultureCode(culture), IsoCode = culture
|
||||
})
|
||||
};
|
||||
|
||||
var result = await domainService.UpdateDomainsAsync(Root.Key, updateModel);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(DomainOperationStatus.Success, result.Status);
|
||||
|
||||
void VerifyDomains(IDomain[] domains)
|
||||
{
|
||||
Assert.AreEqual(3, domains.Length);
|
||||
for (var i = 0; i < domains.Length; i++)
|
||||
{
|
||||
Assert.AreEqual(reversedCultures[i], domains[i].LanguageIsoCode);
|
||||
Assert.AreEqual(GetDomainUrlFromCultureCode(reversedCultures[i]), domains[i].DomainName);
|
||||
}
|
||||
}
|
||||
|
||||
VerifyDomains(result.Result.ToArray());
|
||||
|
||||
// re-get and verify again
|
||||
var domains = await domainService.GetAssignedDomainsAsync(Root.Key, true);
|
||||
VerifyDomains(domains.ToArray());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Remove_All_Domains()
|
||||
{
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var updateModel = new DomainsUpdateModel
|
||||
{
|
||||
Domains = Cultures.Select(culture => new DomainModel
|
||||
{
|
||||
DomainName = GetDomainUrlFromCultureCode(culture), IsoCode = culture
|
||||
})
|
||||
};
|
||||
|
||||
var result = await domainService.UpdateDomainsAsync(Root.Key, updateModel);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(DomainOperationStatus.Success, result.Status);
|
||||
Assert.AreEqual(3, result.Result.Count());
|
||||
|
||||
updateModel.Domains = Enumerable.Empty<DomainModel>();
|
||||
|
||||
result = await domainService.UpdateDomainsAsync(Root.Key, updateModel);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(DomainOperationStatus.Success, result.Status);
|
||||
Assert.AreEqual(0, result.Result.Count());
|
||||
|
||||
// re-get and verify again
|
||||
var domains = await domainService.GetAssignedDomainsAsync(Root.Key, true);
|
||||
Assert.AreEqual(0, domains.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Remove_Single_Domain()
|
||||
{
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var updateModel = new DomainsUpdateModel
|
||||
{
|
||||
Domains = Cultures.Select(culture => new DomainModel
|
||||
{
|
||||
DomainName = GetDomainUrlFromCultureCode(culture), IsoCode = culture
|
||||
})
|
||||
};
|
||||
|
||||
var result = await domainService.UpdateDomainsAsync(Root.Key, updateModel);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(DomainOperationStatus.Success, result.Status);
|
||||
Assert.AreEqual(3, result.Result.Count());
|
||||
|
||||
updateModel.Domains = new[] { updateModel.Domains.First(), updateModel.Domains.Last() };
|
||||
|
||||
result = await domainService.UpdateDomainsAsync(Root.Key, updateModel);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(DomainOperationStatus.Success, result.Status);
|
||||
Assert.AreEqual(2, result.Result.Count());
|
||||
Assert.AreEqual(Cultures.First(), result.Result.First().LanguageIsoCode);
|
||||
Assert.AreEqual(Cultures.Last(), result.Result.Last().LanguageIsoCode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Resolve_Urls_With_Domains_For_All_Cultures()
|
||||
{
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var updateModel = new DomainsUpdateModel
|
||||
{
|
||||
Domains = Cultures.Select(culture => new DomainModel
|
||||
{
|
||||
DomainName = GetDomainUrlFromCultureCode(culture), IsoCode = culture
|
||||
})
|
||||
};
|
||||
|
||||
var result = await domainService.UpdateDomainsAsync(Root.Key, updateModel);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
var rootUrls = GetContentUrlsAsync(Root).ToArray();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
@@ -100,11 +228,21 @@ public class DomainAndUrlsTests : UmbracoIntegrationTest
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Having_three_cultures_but_set_domain_on_a_non_default_language()
|
||||
public async Task Can_Resolve_Urls_For_Non_Default_Domain_Culture_Only()
|
||||
{
|
||||
var culture = Cultures[1];
|
||||
var domain = GetDomainUrlFromCultureCode(culture);
|
||||
SetDomainOnContent(Root, culture, domain);
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var updateModel = new DomainsUpdateModel
|
||||
{
|
||||
Domains = new[]
|
||||
{
|
||||
new DomainModel { DomainName = domain, IsoCode = culture }
|
||||
}
|
||||
};
|
||||
|
||||
var result = await domainService.UpdateDomainsAsync(Root.Key, updateModel);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
var rootUrls = GetContentUrlsAsync(Root).ToArray();
|
||||
|
||||
@@ -124,6 +262,99 @@ public class DomainAndUrlsTests : UmbracoIntegrationTest
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Can_Set_Default_Culture()
|
||||
{
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var culture = Cultures[1];
|
||||
var updateModel = new DomainsUpdateModel
|
||||
{
|
||||
DefaultIsoCode = culture,
|
||||
Domains = Enumerable.Empty<DomainModel>()
|
||||
};
|
||||
|
||||
var result = await domainService.UpdateDomainsAsync(Root.Key, updateModel);
|
||||
Assert.IsTrue(result.Success);
|
||||
Assert.AreEqual(1, result.Result.Count());
|
||||
|
||||
// default culture is represented as a wildcard domain
|
||||
var domain = result.Result.First();
|
||||
Assert.IsTrue(domain.IsWildcard);
|
||||
Assert.AreEqual(culture, domain.LanguageIsoCode);
|
||||
Assert.AreEqual("*" + Root.Id, domain.DomainName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Use_Obsolete_Save()
|
||||
{
|
||||
foreach (var culture in Cultures)
|
||||
{
|
||||
SetDomainOnContent(Root, culture, GetDomainUrlFromCultureCode(culture));
|
||||
}
|
||||
|
||||
var domains = GetRequiredService<IDomainService>().GetAssignedDomains(Root.Id, true);
|
||||
Assert.AreEqual(3, domains.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Use_Obsolete_Delete()
|
||||
{
|
||||
foreach (var culture in Cultures)
|
||||
{
|
||||
SetDomainOnContent(Root, culture, GetDomainUrlFromCultureCode(culture));
|
||||
}
|
||||
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
|
||||
var domains = domainService.GetAssignedDomains(Root.Id, true);
|
||||
Assert.AreEqual(3, domains.Count());
|
||||
|
||||
var result = domainService.Delete(domains.First());
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
domains = domainService.GetAssignedDomains(Root.Id, true);
|
||||
Assert.AreEqual(2, domains.Count());
|
||||
}
|
||||
|
||||
[TestCase("/domain")]
|
||||
[TestCase("/")]
|
||||
[TestCase("some.domain.com")]
|
||||
public async Task Cannot_Assign_Duplicate_Domains(string domainName)
|
||||
{
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var updateModel = new DomainsUpdateModel
|
||||
{
|
||||
Domains = Cultures.Select(culture => new DomainModel { DomainName = domainName, IsoCode = culture }).ToArray()
|
||||
};
|
||||
|
||||
var result = await domainService.UpdateDomainsAsync(Root.Key, updateModel);
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(DomainOperationStatus.DuplicateDomainName, result.Status);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_Assign_Already_Used_Domains()
|
||||
{
|
||||
var copy = ContentService.Copy(Root, Root.ParentId, false);
|
||||
ContentService.SaveAndPublish(copy!);
|
||||
|
||||
var domainService = GetRequiredService<IDomainService>();
|
||||
var updateModel = new DomainsUpdateModel
|
||||
{
|
||||
Domains = Cultures.Select(culture => new DomainModel
|
||||
{
|
||||
DomainName = GetDomainUrlFromCultureCode(culture), IsoCode = culture
|
||||
})
|
||||
};
|
||||
|
||||
var result = await domainService.UpdateDomainsAsync(Root.Key, updateModel);
|
||||
Assert.IsTrue(result.Success);
|
||||
|
||||
result = await domainService.UpdateDomainsAsync(copy.Key, updateModel);
|
||||
Assert.IsFalse(result.Success);
|
||||
Assert.AreEqual(DomainOperationStatus.DuplicateDomainName, result.Status);
|
||||
}
|
||||
|
||||
private static string GetDomainUrlFromCultureCode(string culture) =>
|
||||
"/" + culture.Replace("-", string.Empty).ToLower() + "/";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user