Sort domains (Culture and Hostnames) (#13797)

* Add sort order to IDomain, UmbracoDomain and DomainDto

* Add migration to create domain sort order column

* Add Sort method to domain service

* Set sort order when persisting new domain and order results

* Add multiple and block style support to umb-button-group

* Allow sorting domains in back-office, improve UI and rewrite PostSaveLanguageAndDomains for correctly sorting domains

* Ensure routing and cache keeps the domain sort order

* Update test to assert correct domain order

* Move migration to target 11.3 and cleanup plan

* Fix formatting/styling and make SelectDomains private

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>

---------

Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com>
This commit is contained in:
Ronald Barendse
2023-02-14 10:35:45 +01:00
committed by GitHub
parent 7348171c01
commit 45036f54dd
25 changed files with 858 additions and 690 deletions

View File

@@ -7,40 +7,36 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Querying;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Cms.Infrastructure.Persistence.Factories;
using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
// TODO: We need to get a readonly ISO code for the domain assigned
internal class DomainRepository : EntityRepositoryBase<int, IDomain>, IDomainRepository
{
public DomainRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger<DomainRepository> logger)
: base(scopeAccessor, cache, logger)
{
}
{ }
public IDomain? GetByName(string domainName) =>
GetMany().FirstOrDefault(x => x.DomainName.InvariantEquals(domainName));
public IDomain? GetByName(string domainName)
=> GetMany().FirstOrDefault(x => x.DomainName.InvariantEquals(domainName));
public bool Exists(string domainName) => GetMany().Any(x => x.DomainName.InvariantEquals(domainName));
public bool Exists(string domainName)
=> GetMany().Any(x => x.DomainName.InvariantEquals(domainName));
public IEnumerable<IDomain> GetAll(bool includeWildcards) =>
GetMany().Where(x => includeWildcards || x.IsWildcard == false);
public IEnumerable<IDomain> GetAll(bool includeWildcards)
=> GetMany().Where(x => includeWildcards || x.IsWildcard == false);
public IEnumerable<IDomain> GetAssignedDomains(int contentId, bool includeWildcards) =>
GetMany()
.Where(x => x.RootContentId == contentId)
.Where(x => includeWildcards || x.IsWildcard == false);
public IEnumerable<IDomain> GetAssignedDomains(int contentId, bool includeWildcards)
=> GetMany().Where(x => x.RootContentId == contentId).Where(x => includeWildcards || x.IsWildcard == false);
protected override IRepositoryCachePolicy<IDomain, int> CreateCachePolicy() =>
new FullDataSetRepositoryCachePolicy<IDomain, int>(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/
false);
protected override IRepositoryCachePolicy<IDomain, int> CreateCachePolicy()
=> new FullDataSetRepositoryCachePolicy<IDomain, int>(GlobalIsolatedCache, ScopeAccessor, GetEntityId, false);
protected override IDomain? PerformGet(int id) =>
// use the underlying GetAll which will force cache all domains
GetMany().FirstOrDefault(x => x.Id == id);
protected override IDomain? PerformGet(int id)
// Use the underlying GetAll which will force cache all domains
=> GetMany().FirstOrDefault(x => x.Id == id);
protected override IEnumerable<IDomain> PerformGetAll(params int[]? ids)
{
@@ -49,12 +45,13 @@ internal class DomainRepository : EntityRepositoryBase<int, IDomain>, IDomainRep
{
sql.WhereIn<DomainDto>(x => x.Id, ids);
}
sql.OrderBy<DomainDto>(dto => dto.SortOrder);
return Database.Fetch<DomainDto>(sql).Select(ConvertFromDto);
return Database.Fetch<DomainDto>(sql).Select(DomainFactory.BuildEntity);
}
protected override IEnumerable<IDomain> PerformGetByQuery(IQuery<IDomain> query) =>
throw new NotSupportedException("This repository does not support this method");
protected override IEnumerable<IDomain> PerformGetByQuery(IQuery<IDomain> query)
=> throw new NotSupportedException("This repository does not support this method");
protected override Sql<ISqlContext> GetBaseQuery(bool isCount)
{
@@ -65,7 +62,7 @@ internal class DomainRepository : EntityRepositoryBase<int, IDomain>, IDomainRep
}
else
{
sql.Select("umbracoDomain.*, umbracoLanguage.languageISOCode")
sql.Select($"{Constants.DatabaseSchema.Tables.Domain}.*, {Constants.DatabaseSchema.Tables.Language}.languageISOCode")
.From<DomainDto>()
.LeftJoin<LanguageDto>()
.On<DomainDto, LanguageDto>(dto => dto.DefaultLanguage, dto => dto.Id);
@@ -74,23 +71,23 @@ internal class DomainRepository : EntityRepositoryBase<int, IDomain>, IDomainRep
return sql;
}
protected override string GetBaseWhereClause() => $"{Constants.DatabaseSchema.Tables.Domain}.id = @id";
protected override string GetBaseWhereClause()
=> $"{Constants.DatabaseSchema.Tables.Domain}.id = @id";
protected override IEnumerable<string> GetDeleteClauses()
{
var list = new List<string> { "DELETE FROM umbracoDomain WHERE id = @id" };
return list;
}
=> new []
{
$"DELETE FROM {Constants.DatabaseSchema.Tables.Domain} WHERE id = @id",
};
protected override void PersistNewItem(IDomain entity)
{
var exists = Database.ExecuteScalar<int>(
"SELECT COUNT(*) FROM umbracoDomain WHERE domainName = @domainName",
$"SELECT COUNT(*) FROM {Constants.DatabaseSchema.Tables.Domain} WHERE domainName = @domainName",
new { domainName = entity.DomainName });
if (exists > 0)
{
throw new DuplicateNameException(
string.Format("The domain name {0} is already assigned", entity.DomainName));
throw new DuplicateNameException($"The domain name {entity.DomainName} is already assigned.");
}
if (entity.RootContentId.HasValue)
@@ -100,34 +97,37 @@ internal class DomainRepository : EntityRepositoryBase<int, IDomain>, IDomainRep
new { id = entity.RootContentId.Value });
if (contentExists == 0)
{
throw new NullReferenceException("No content exists with id " + entity.RootContentId.Value);
throw new NullReferenceException($"No content exists with id {entity.RootContentId.Value}.");
}
}
if (entity.LanguageId.HasValue)
{
var languageExists = Database.ExecuteScalar<int>(
"SELECT COUNT(*) FROM umbracoLanguage WHERE id = @id",
$"SELECT COUNT(*) FROM {Constants.DatabaseSchema.Tables.Language} WHERE id = @id",
new { id = entity.LanguageId.Value });
if (languageExists == 0)
{
throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value);
throw new NullReferenceException($"No language exists with id {entity.LanguageId.Value}.");
}
}
entity.AddingEntity();
var factory = new DomainModelFactory();
DomainDto dto = factory.BuildDto(entity);
// Get sort order
entity.SortOrder = GetNewSortOrder(entity.RootContentId, entity.IsWildcard);
DomainDto dto = DomainFactory.BuildDto(entity);
var id = Convert.ToInt32(Database.Insert(dto));
entity.Id = id;
// if the language changed, we need to resolve the ISO code!
// If the language changed, we need to resolve the ISO code
if (entity.LanguageId.HasValue)
{
((UmbracoDomain)entity).LanguageIsoCode = Database.ExecuteScalar<string>(
"SELECT languageISOCode FROM umbracoLanguage WHERE id=@langId", new { langId = entity.LanguageId });
$"SELECT languageISOCode FROM {Constants.DatabaseSchema.Tables.Language} WHERE id = @langId",
new { langId = entity.LanguageId });
}
entity.ResetDirtyProperties();
@@ -137,15 +137,13 @@ internal class DomainRepository : EntityRepositoryBase<int, IDomain>, IDomainRep
{
entity.UpdatingEntity();
// Ensure there is no other domain with the same name on another entity
var exists = Database.ExecuteScalar<int>(
"SELECT COUNT(*) FROM umbracoDomain WHERE domainName = @domainName AND umbracoDomain.id <> @id",
$"SELECT COUNT(*) FROM {Constants.DatabaseSchema.Tables.Domain} WHERE domainName = @domainName AND umbracoDomain.id <> @id",
new { domainName = entity.DomainName, id = entity.Id });
// ensure there is no other domain with the same name on another entity
if (exists > 0)
{
throw new DuplicateNameException(
string.Format("The domain name {0} is already assigned", entity.DomainName));
throw new DuplicateNameException($"The domain name {entity.DomainName} is already assigned.");
}
if (entity.RootContentId.HasValue)
@@ -155,69 +153,40 @@ internal class DomainRepository : EntityRepositoryBase<int, IDomain>, IDomainRep
new { id = entity.RootContentId.Value });
if (contentExists == 0)
{
throw new NullReferenceException("No content exists with id " + entity.RootContentId.Value);
throw new NullReferenceException($"No content exists with id {entity.RootContentId.Value}.");
}
}
if (entity.LanguageId.HasValue)
{
var languageExists = Database.ExecuteScalar<int>(
"SELECT COUNT(*) FROM umbracoLanguage WHERE id = @id",
$"SELECT COUNT(*) FROM {Constants.DatabaseSchema.Tables.Language} WHERE id = @id",
new { id = entity.LanguageId.Value });
if (languageExists == 0)
{
throw new NullReferenceException("No language exists with id " + entity.LanguageId.Value);
throw new NullReferenceException($"No language exists with id {entity.LanguageId.Value}.");
}
}
var factory = new DomainModelFactory();
DomainDto dto = factory.BuildDto(entity);
DomainDto dto = DomainFactory.BuildDto(entity);
Database.Update(dto);
// if the language changed, we need to resolve the ISO code!
// If the language changed, we need to resolve the ISO code
if (entity.WasPropertyDirty("LanguageId"))
{
((UmbracoDomain)entity).LanguageIsoCode = Database.ExecuteScalar<string>(
"SELECT languageISOCode FROM umbracoLanguage WHERE id=@langId", new { langId = entity.LanguageId });
$"SELECT languageISOCode FROM {Constants.DatabaseSchema.Tables.Language} WHERE id = @langId",
new { langId = entity.LanguageId });
}
entity.ResetDirtyProperties();
}
private IDomain ConvertFromDto(DomainDto dto)
{
var factory = new DomainModelFactory();
IDomain entity = factory.BuildEntity(dto);
return entity;
}
internal class DomainModelFactory
{
public IDomain BuildEntity(DomainDto dto)
{
var domain = new UmbracoDomain(dto.DomainName, dto.IsoCode)
{
Id = dto.Id,
LanguageId = dto.DefaultLanguage,
RootContentId = dto.RootStructureId,
};
// reset dirty initial properties (U4-1946)
domain.ResetDirtyProperties(false);
return domain;
}
public DomainDto BuildDto(IDomain entity)
{
var dto = new DomainDto
{
DefaultLanguage = entity.LanguageId,
DomainName = entity.DomainName,
Id = entity.Id,
RootStructureId = entity.RootContentId,
};
return dto;
}
}
protected int GetNewSortOrder(int? rootContentId, bool isWildcard)
=> isWildcard
? -1
: Database.ExecuteScalar<int>(
$"SELECT COALESCE(MAX(sortOrder), -1) + 1 FROM {Constants.DatabaseSchema.Tables.Domain} WHERE domainRootStructureID = @rootContentId AND NOT (domainName = '' OR domainName LIKE '*%')",
new { rootContentId });
}