Skip to content

Commit

Permalink
Implemented field type search.
Browse files Browse the repository at this point in the history
  • Loading branch information
Utar94 committed Jul 20, 2024
1 parent fecd780 commit ddfa581
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 1 deletion.
8 changes: 8 additions & 0 deletions backend/src/Logitar.Cms.Contracts/FieldTypes/FieldTypeSort.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Logitar.Cms.Contracts.FieldTypes;

public enum FieldTypeSort
{
DisplayName,
UniqueName,
UpdatedOn
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Logitar.Cms.Contracts.Search;

namespace Logitar.Cms.Contracts.FieldTypes;

public record FieldTypeSortOption : SortOption
{
public new FieldTypeSort Field
{
get => Enum.Parse<FieldTypeSort>(base.Field);
set => base.Field = value.ToString();
}

public FieldTypeSortOption() : this(FieldTypeSort.UpdatedOn, isDescending: true)
{
}

public FieldTypeSortOption(FieldTypeSort field, bool isDescending = false) : base(field.ToString(), isDescending)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Logitar.Cms.Contracts.Search;

namespace Logitar.Cms.Contracts.FieldTypes;

public record SearchFieldTypesPayload : SearchPayload
{
public DataType? DataType { get; set; }

public new List<FieldTypeSortOption>? Sort { get; set; }
}
3 changes: 3 additions & 0 deletions backend/src/Logitar.Cms.Core/FieldTypes/IFieldTypeQuerier.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Logitar.Cms.Contracts.FieldTypes;
using Logitar.Cms.Contracts.Search;

namespace Logitar.Cms.Core.FieldTypes;

Expand All @@ -8,4 +9,6 @@ public interface IFieldTypeQuerier
Task<FieldType?> ReadAsync(FieldTypeId id, CancellationToken cancellationToken = default);
Task<FieldType?> ReadAsync(Guid id, CancellationToken cancellationToken = default);
Task<FieldType?> ReadAsync(string uniqueName, CancellationToken cancellationToken = default);

Task<SearchResults<FieldType>> SearchAsync(SearchFieldTypesPayload payload, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace Logitar.Cms.Core.FieldTypes;

public interface IFieldTypeRepository
{
Task<IReadOnlyCollection<FieldTypeAggregate>> LoadAsync(CancellationToken cancellationToken = default);
Task<FieldTypeAggregate?> LoadAsync(FieldTypeId id, CancellationToken cancellationToken = default);
Task<IReadOnlyCollection<FieldTypeAggregate>> LoadAsync(IEnumerable<FieldTypeId> ids, CancellationToken cancellationToken = default);
Task<FieldTypeAggregate?> LoadAsync(UniqueNameUnit uniqueName, CancellationToken cancellationToken = default);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Logitar.Cms.Contracts.FieldTypes;
using Logitar.Cms.Contracts.Search;
using MediatR;

namespace Logitar.Cms.Core.FieldTypes.Queries;

public record SearchFieldTypesQuery(SearchFieldTypesPayload Payload) : IRequest<SearchResults<FieldType>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Logitar.Cms.Contracts.FieldTypes;
using Logitar.Cms.Contracts.Search;
using MediatR;

namespace Logitar.Cms.Core.FieldTypes.Queries;

internal class SearchFieldTypesQueryHandler : IRequestHandler<SearchFieldTypesQuery, SearchResults<FieldType>>
{
private readonly IFieldTypeQuerier _fieldTypeQuerier;

public SearchFieldTypesQueryHandler(IFieldTypeQuerier fieldTypeQuerier)
{
_fieldTypeQuerier = fieldTypeQuerier;
}

public async Task<SearchResults<FieldType>> Handle(SearchFieldTypesQuery query, CancellationToken cancellationToken)
{
return await _fieldTypeQuerier.SearchAsync(query.Payload, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using Logitar.Cms.Contracts.Actors;
using Logitar.Cms.Contracts.FieldTypes;
using Logitar.Cms.Contracts.Search;
using Logitar.Cms.Core.FieldTypes;
using Logitar.Cms.EntityFrameworkCore.Actors;
using Logitar.Cms.EntityFrameworkCore.Entities;
using Logitar.Data;
using Logitar.EventSourcing;
using Logitar.Identity.EntityFrameworkCore.Relational;
using Microsoft.EntityFrameworkCore;

namespace Logitar.Cms.EntityFrameworkCore.Queriers;
Expand All @@ -12,11 +15,15 @@ internal class FieldTypeQuerier : IFieldTypeQuerier
{
private readonly IActorService _actorService;
private readonly DbSet<FieldTypeEntity> _fieldTypes;
private readonly ISearchHelper _searchHelper;
private readonly ISqlHelper _sqlHelper;

public FieldTypeQuerier(IActorService actorService, CmsContext context)
public FieldTypeQuerier(IActorService actorService, CmsContext context, ISearchHelper searchHelper, ISqlHelper sqlHelper)
{
_actorService = actorService;
_fieldTypes = context.FieldTypes;
_searchHelper = searchHelper;
_sqlHelper = sqlHelper;
}

public async Task<FieldType> ReadAsync(FieldTypeAggregate fieldType, CancellationToken cancellationToken)
Expand Down Expand Up @@ -46,6 +53,56 @@ public async Task<FieldType> ReadAsync(FieldTypeAggregate fieldType, Cancellatio
return fieldType == null ? null : await MapAsync(fieldType, cancellationToken);
}

public async Task<SearchResults<FieldType>> SearchAsync(SearchFieldTypesPayload payload, CancellationToken cancellationToken)
{
IQueryBuilder builder = _sqlHelper.QueryFrom(CmsDb.FieldTypes.Table).SelectAll(CmsDb.FieldTypes.Table)
.ApplyIdInFilter(CmsDb.FieldTypes.UniqueId, payload);
_searchHelper.ApplyTextSearch(builder, payload.Search, CmsDb.FieldTypes.UniqueName, CmsDb.FieldTypes.DisplayName);

if (payload.DataType.HasValue)
{
builder.Where(CmsDb.FieldTypes.DataType, Operators.IsEqualTo(payload.DataType.Value.ToString()));
}

IQueryable<FieldTypeEntity> query = _fieldTypes.FromQuery(builder).AsNoTracking();

long total = await query.LongCountAsync(cancellationToken);

IOrderedQueryable<FieldTypeEntity>? ordered = null;
if (payload.Sort != null)
{
foreach (FieldTypeSortOption sort in payload.Sort)
{
switch (sort.Field)
{
case FieldTypeSort.DisplayName:
ordered = (ordered == null)
? (sort.IsDescending ? query.OrderByDescending(x => x.DisplayName) : query.OrderBy(x => x.DisplayName))
: (sort.IsDescending ? ordered.ThenByDescending(x => x.DisplayName) : ordered.ThenBy(x => x.DisplayName));
break;
case FieldTypeSort.UniqueName:
ordered = (ordered == null)
? (sort.IsDescending ? query.OrderByDescending(x => x.UniqueName) : query.OrderBy(x => x.UniqueName))
: (sort.IsDescending ? ordered.ThenByDescending(x => x.UniqueName) : ordered.ThenBy(x => x.UniqueName));
break;
case FieldTypeSort.UpdatedOn:
ordered = (ordered == null)
? (sort.IsDescending ? query.OrderByDescending(x => x.UpdatedOn) : query.OrderBy(x => x.UpdatedOn))
: (sort.IsDescending ? ordered.ThenByDescending(x => x.UpdatedOn) : ordered.ThenBy(x => x.UpdatedOn));
break;
}
}
}
query = ordered ?? query;

query = query.ApplyPaging(payload);

FieldTypeEntity[] fieldTypes = await query.ToArrayAsync(cancellationToken);
IEnumerable<FieldType> items = await MapAsync(fieldTypes, cancellationToken);

return new SearchResults<FieldType>(items, total);
}

private async Task<FieldType> MapAsync(FieldTypeEntity fieldType, CancellationToken cancellationToken)
=> (await MapAsync([fieldType], cancellationToken)).Single();
private async Task<IReadOnlyCollection<FieldType>> MapAsync(IEnumerable<FieldTypeEntity> fieldTypes, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public FieldTypeRepository(IEventBus eventBus, EventContext eventContext, IEvent
_sqlHelper = sqlHelper;
}

public async Task<IReadOnlyCollection<FieldTypeAggregate>> LoadAsync(CancellationToken cancellationToken)
{
return (await base.LoadAsync<FieldTypeAggregate>(cancellationToken)).ToArray();
}

public async Task<FieldTypeAggregate?> LoadAsync(FieldTypeId id, CancellationToken cancellationToken)
{
return await base.LoadAsync<FieldTypeAggregate>(id.AggregateId, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Logitar.Cms.Contracts.FieldTypes;
using Logitar.Cms.Contracts.Search;
using Logitar.Cms.Core;
using Logitar.Cms.Core.FieldTypes.Commands;
using Logitar.Cms.Core.FieldTypes.Queries;
using Logitar.Cms.Web.Models.FieldTypes;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

Expand Down Expand Up @@ -41,4 +43,11 @@ public async Task<ActionResult<FieldType>> ReadAsync(string uniqueName, Cancella
FieldType? fieldType = await _pipeline.ExecuteAsync(new ReadFieldTypeQuery(Id: null, uniqueName), cancellationToken);
return fieldType == null ? NotFound() : Ok(fieldType);
}

[HttpGet]
public async Task<ActionResult<SearchResults<FieldType>>> SearchAsync([FromQuery] SearchFieldTypesParameters parameters, CancellationToken cancellationToken)
{
SearchResults<FieldType> fieldTypes = await _pipeline.ExecuteAsync(new SearchFieldTypesQuery(parameters.ToPayload()), cancellationToken);
return Ok(fieldTypes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Logitar.Cms.Contracts.FieldTypes;
using Logitar.Cms.Contracts.Search;
using Logitar.Cms.Web.Models.Search;
using Microsoft.AspNetCore.Mvc;

namespace Logitar.Cms.Web.Models.FieldTypes;

public record SearchFieldTypesParameters : SearchParameters
{
[FromQuery(Name = "type")]
public DataType? DataType { get; set; }

public SearchFieldTypesPayload ToPayload()
{
SearchFieldTypesPayload payload = new()
{
DataType = DataType
};

FillPayload(payload);

List<SortOption>? sortOptions = ((SearchPayload)payload).Sort;
if (sortOptions != null)
{
payload.Sort = new List<FieldTypeSortOption>(capacity: sortOptions.Count);
foreach (SortOption sort in sortOptions)
{
if (Enum.TryParse(sort.Field, out FieldTypeSort field))
{
payload.Sort.Add(new FieldTypeSortOption(field, sort.IsDescending));
}
}
}

return payload;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using Logitar.Cms.Contracts.FieldTypes;
using Logitar.Cms.Contracts.Search;
using Logitar.Cms.Core.FieldTypes.Properties;
using Logitar.Identity.Contracts.Settings;
using Logitar.Identity.Domain.Shared;
using Microsoft.Extensions.DependencyInjection;

namespace Logitar.Cms.Core.FieldTypes.Queries;

[Trait(Traits.Category, Categories.Integration)]
public class SearchFieldTypesQueryHandlerTests : IntegrationTests
{
private readonly IFieldTypeRepository _fieldTypeRepository;

private readonly FieldTypeAggregate _contents;
private readonly FieldTypeAggregate _metaTitle;
private readonly FieldTypeAggregate _slug;
private readonly FieldTypeAggregate _subTitle;
private readonly FieldTypeAggregate _title;

public SearchFieldTypesQueryHandlerTests() : base()
{
_fieldTypeRepository = ServiceProvider.GetRequiredService<IFieldTypeRepository>();

IUniqueNameSettings uniqueNameSettings = FieldTypeAggregate.UniqueNameSettings;
_contents = new(new UniqueNameUnit(uniqueNameSettings, "ArticleContents"), new ReadOnlyTextProperties());
_metaTitle = new(new UniqueNameUnit(uniqueNameSettings, "ArticleMetaTitle"), new ReadOnlyStringProperties());
_slug = new(new UniqueNameUnit(uniqueNameSettings, "Slug"), new ReadOnlyStringProperties());
_subTitle = new(new UniqueNameUnit(uniqueNameSettings, "ArticleSubTitle"), new ReadOnlyStringProperties());
_title = new(new UniqueNameUnit(uniqueNameSettings, "ArticleTitle"), new ReadOnlyStringProperties());
}

public override async Task InitializeAsync()
{
await base.InitializeAsync();

await _fieldTypeRepository.SaveAsync([_contents, _metaTitle, _slug, _subTitle, _title]);
}

[Fact(DisplayName = "It should return empty results when no field type matches.")]
public async Task It_should_return_empty_results_when_no_field_type_matches()
{
SearchFieldTypesPayload payload = new()
{
Search = new TextSearch([new SearchTerm("%test%")])
};
SearchFieldTypesQuery query = new(payload);

SearchResults<FieldType> results = await Pipeline.ExecuteAsync(query);

Assert.Empty(results.Items);
Assert.Equal(0, results.Total);
}

[Fact(DisplayName = "It should return the correct matching field types.")]
public async Task It_should_return_the_correct_matching_field_types()
{
SearchFieldTypesPayload payload = new()
{
DataType = DataType.String,
IdIn = (await _fieldTypeRepository.LoadAsync()).Select(fieldType => fieldType.Id.ToGuid()).ToList(),
Search = new TextSearch([new SearchTerm("%title"), new SearchTerm("con%")], SearchOperator.Or),
Sort = [new FieldTypeSortOption(FieldTypeSort.UniqueName, isDescending: false)],
Skip = 1,
Limit = 1
};
payload.IdIn.Add(Guid.Empty);
payload.IdIn.Remove(_metaTitle.Id.ToGuid());
SearchFieldTypesQuery query = new(payload);

SearchResults<FieldType> results = await Pipeline.ExecuteAsync(query);

Assert.Equal(2, results.Total);
FieldType fieldType = Assert.Single(results.Items);
Assert.Equal(_title.Id.ToGuid(), fieldType.Id);
}
}

0 comments on commit ddfa581

Please sign in to comment.