Skip to content

Commit

Permalink
Noen forslag (#1091)
Browse files Browse the repository at this point in the history
Nå ble dette litt mer enn noen forslag om akkurat PRen din. Jeg så noe
lavt hengende frukt i PaginatedList også som jeg håper du syns er greit
at jeg bare slenger inn her 🙏
  • Loading branch information
MagnusSandgren authored Sep 6, 2024
1 parent 1c0ac9d commit 72b2f68
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -1,49 +1,57 @@
using System.Globalization;
using System.Text;
using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities;
using Digdir.Domain.Dialogporten.Domain.SubjectResources;
using Microsoft.EntityFrameworkCore;

namespace Digdir.Domain.Dialogporten.Application.Common.Extensions;

public static class DbSetExtensions
{
public static IQueryable<DialogEntity> PrefilterAuthorizedDialogs(this DbSet<DialogEntity> dialogs,
Func<Task<DialogSearchAuthorizationResult?>> authorizedResourcesProvider)
{
var authorizedResources = authorizedResourcesProvider().GetAwaiter().GetResult();
return authorizedResources is null ? dialogs : dialogs.PrefilterAuthorizedDialogs(authorizedResources);
}

public static IQueryable<DialogEntity> PrefilterAuthorizedDialogs(this DbSet<DialogEntity> dialogs, DialogSearchAuthorizationResult authorizedResources)
{
var parameters = new List<object>();
var sb = new StringBuilder().Append("SELECT * FROM \"Dialog\" WHERE 1=1");

foreach (var item in authorizedResources.ResourcesByParties)
// lang=sql
var sb = new StringBuilder()
.AppendLine(CultureInfo.InvariantCulture, $"""
SELECT *
FROM "Dialog"
WHERE "Id" = ANY(@p{parameters.Count})
""");
parameters.Add(authorizedResources.DialogIds);

foreach (var (party, resources) in authorizedResources.ResourcesByParties)
{
sb.Append(" OR (\"Party\" = @p")
.Append(parameters.Count)
.Append(" AND \"ServiceResource\" = ANY(@p")
.Append(parameters.Count + 1).Append("))");
parameters.Add(item.Key);
parameters.Add(item.Value);
// lang=sql
sb.AppendLine(CultureInfo.InvariantCulture, $"""
OR (
"{nameof(DialogEntity.Party)}" = @p{parameters.Count}
AND "{nameof(DialogEntity.ServiceResource)}" = ANY(@p{parameters.Count + 1})
)
""");
parameters.Add(party);
parameters.Add(resources);
}

foreach (var item in authorizedResources.SubjectsByParties)
foreach (var (party, subjects) in authorizedResources.SubjectsByParties)
{
sb.Append(" OR (\"Party\" = @p")
.Append(parameters.Count)
.Append(" AND \"ServiceResource\" = ANY(SELECT r.\"Resource\" FROM \"SubjectResource\" r WHERE r.\"Subject\" = ANY(@p")
.Append(parameters.Count + 1).Append(")))");
parameters.Add(item.Key);
parameters.Add(item.Value);
// lang=sql
sb.AppendLine(CultureInfo.InvariantCulture, $"""
OR (
"{nameof(DialogEntity.Party)}" = @p{parameters.Count}
AND "{nameof(DialogEntity.ServiceResource)}" = ANY(
SELECT "{nameof(SubjectResource.Resource)}"
FROM "{nameof(SubjectResource)}"
WHERE "{nameof(SubjectResource.Subject)}" = ANY(@p{parameters.Count + 1})
)
)
""");
parameters.Add(party);
parameters.Add(subjects);
}

sb.Append(" OR \"Id\" = ANY(@p")
.Append(parameters.Count)
.Append(')');
parameters.Add(authorizedResources.DialogIds);

return dialogs.FromSqlRaw(sb.ToString(), parameters.ToArray());
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace Digdir.Domain.Dialogporten.Application.Common.Pagination;
using Digdir.Domain.Dialogporten.Application.Common.Pagination.Extensions;
using Digdir.Domain.Dialogporten.Application.Common.Pagination.OrderOption;

namespace Digdir.Domain.Dialogporten.Application.Common.Pagination;

public sealed class PaginatedList<T>
{
Expand Down Expand Up @@ -31,4 +34,34 @@ public PaginatedList(IEnumerable<T> items, bool hasNextPage, string? @continue,
OrderBy = orderBy;
Items = items?.ToList() ?? throw new ArgumentNullException(nameof(items));
}

/// <summary>
/// Converts the items in the paginated list to a different type.
/// </summary>
/// <typeparam name="TDestination">The type to convert the items to.</typeparam>
/// <param name="map">A function to convert each item to the new type.</param>
/// <returns>A new <see cref="PaginatedList{TDestination}"/> with the converted items.</returns>
public PaginatedList<TDestination> ConvertTo<TDestination>(Func<T, TDestination> map)
{
return new PaginatedList<TDestination>(
Items.Select(map),
HasNextPage,
ContinuationToken,
OrderBy);
}

/// <summary>
/// Creates an empty paginated list based on the provided pagination parameters.
/// </summary>
/// <typeparam name="TOrderDefinition">The type of the order definition.</typeparam>
/// <typeparam name="TTarget">The type of the target.</typeparam>
/// <param name="parameter">The sortable pagination parameter.</param>
/// <returns>An empty <see cref="PaginatedList{T}"/>.</returns>
public static PaginatedList<T> CreateEmpty<TOrderDefinition, TTarget>(SortablePaginationParameter<TOrderDefinition, TTarget> parameter)
where TOrderDefinition : IOrderDefinition<TTarget> =>
new(
items: [],
hasNextPage: false,
@continue: parameter.ContinuationToken?.Raw,
orderBy: parameter.OrderBy.DefaultIfNull().GetOrderString());
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public async Task<SearchDialogResult> Handle(SearchDialogQuery request, Cancella

if (authorizedResources.HasNoAuthorizations)
{
return new PaginatedList<SearchDialogDto>([], false, null, request.OrderBy.DefaultIfNull().GetOrderString());
return PaginatedList<SearchDialogDto>.CreateEmpty(request);
}

var paginatedList = await _db.Dialogs
Expand Down Expand Up @@ -179,8 +179,6 @@ public async Task<SearchDialogResult> Handle(SearchDialogQuery request, Cancella
seenLog.IsCurrentEndUser = IdentifierMasker.GetMaybeMaskedIdentifier(currentUserInfo.UserId.ExternalIdWithPrefix) == seenLog.SeenBy.ActorId;
}

var mappedItems = paginatedList.Items.Select(_mapper.Map<SearchDialogDto>).ToList();
return new PaginatedList<SearchDialogDto>(mappedItems, paginatedList.HasNextPage,
paginatedList.ContinuationToken, paginatedList.OrderBy);
return paginatedList.ConvertTo(_mapper.Map<SearchDialogDto>);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,22 @@ public async Task<SearchDialogResult> Handle(SearchDialogQuery request, Cancella
var resourceIds = await _userResourceRegistry.GetCurrentUserResourceIds(cancellationToken);
var searchExpression = Expressions.LocalizedSearchExpression(request.Search, request.SearchLanguageCode);

var query = _db.Dialogs
.PrefilterAuthorizedDialogs(async () => request.EndUserId is null
? null
: await _altinnAuthorization.GetAuthorizedResourcesForSearch(
request.Party ?? [],
request.ServiceResource ?? [],
request.EndUserId,
cancellationToken))
var dialogQuery = _db.Dialogs.AsQueryable();

// If the service owner impersonates an end user, we need to filter the dialogs
// based on the end user's authorization, not the service owner's (which is
// allowed to see everything about every service resource they own).
if (request.EndUserId is not null)
{
var authorizedResources = await _altinnAuthorization.GetAuthorizedResourcesForSearch(
request.Party ?? [],
request.ServiceResource ?? [],
request.EndUserId,
cancellationToken);
dialogQuery = _db.Dialogs.PrefilterAuthorizedDialogs(authorizedResources);
}

var paginatedList = await dialogQuery
.Include(x => x.Content)
.ThenInclude(x => x.Value.Localizations)
.WhereIf(!request.ServiceResource.IsNullOrEmpty(),
Expand All @@ -170,9 +178,7 @@ public async Task<SearchDialogResult> Handle(SearchDialogQuery request, Cancella
x.Content.Any(x => x.Value.Localizations.AsQueryable().Any(searchExpression)) ||
x.SearchTags.Any(x => EF.Functions.ILike(x.Value, request.Search!))
)
.Where(x => resourceIds.Contains(x.ServiceResource));

var paginatedList = await query
.Where(x => resourceIds.Contains(x.ServiceResource))
.ProjectTo<IntermediateSearchDialogDto>(_mapper.ConfigurationProvider)
.ToPaginatedListAsync(request, cancellationToken: cancellationToken);

Expand All @@ -184,8 +190,6 @@ public async Task<SearchDialogResult> Handle(SearchDialogQuery request, Cancella
}
}

var mappedItems = _mapper.Map<List<SearchDialogDto>>(paginatedList.Items).ToList();
return new PaginatedList<SearchDialogDto>(mappedItems, paginatedList.HasNextPage,
paginatedList.ContinuationToken, paginatedList.OrderBy);
return paginatedList.ConvertTo(_mapper.Map<SearchDialogDto>);
}
}

0 comments on commit 72b2f68

Please sign in to comment.