mirror of
https://github.com/alex289/CleanArchitecture.git
synced 2025-06-30 02:31:08 +00:00
feat: Deleted query filter
This commit is contained in:
parent
bfe40e180e
commit
4b2619f13c
@ -37,7 +37,8 @@ public sealed class TenantController : ApiController
|
||||
[FromQuery] PageQuery query,
|
||||
[FromQuery] string searchTerm = "",
|
||||
[FromQuery] bool includeDeleted = false,
|
||||
[FromQuery, SortableFieldsAttribute<TenantViewModelSortProvider, TenantViewModel, Tenant>] SortQuery? sortQuery = null)
|
||||
[FromQuery] [SortableFieldsAttribute<TenantViewModelSortProvider, TenantViewModel, Tenant>]
|
||||
SortQuery? sortQuery = null)
|
||||
{
|
||||
var tenants = await _tenantService.GetAllTenantsAsync(
|
||||
query,
|
||||
|
@ -37,7 +37,8 @@ public sealed class UserController : ApiController
|
||||
[FromQuery] PageQuery query,
|
||||
[FromQuery] string searchTerm = "",
|
||||
[FromQuery] bool includeDeleted = false,
|
||||
[FromQuery, SortableFieldsAttribute<UserViewModelSortProvider, UserViewModel, User>] SortQuery? sortQuery = null)
|
||||
[FromQuery] [SortableFieldsAttribute<UserViewModelSortProvider, UserViewModel, User>]
|
||||
SortQuery? sortQuery = null)
|
||||
{
|
||||
var users = await _userService.GetAllUsersAsync(
|
||||
query,
|
||||
|
@ -4,7 +4,7 @@ using CleanArchitecture.Application.ViewModels.Sorting;
|
||||
|
||||
namespace CleanArchitecture.Api.Swagger;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class SortableFieldsAttribute<TSortingProvider, TViewModel, TEntity>
|
||||
: SwaggerSortableFieldsAttribute
|
||||
where TSortingProvider : ISortingExpressionProvider<TViewModel, TEntity>, new()
|
||||
|
@ -26,7 +26,6 @@ public sealed class SortableFieldsAttributeFilter : IParameterFilter
|
||||
var description = string.Join("<br/>", attribute.GetFields().Order());
|
||||
|
||||
parameter.Description = $"{parameter.Description}<br/><br/>" +
|
||||
$"**Allowed values:**<br/>{description}";
|
||||
$"**Allowed values:**<br/>{description}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -6,4 +6,4 @@ namespace CleanArchitecture.Api.Swagger;
|
||||
public abstract class SwaggerSortableFieldsAttribute : Attribute
|
||||
{
|
||||
public abstract IEnumerable<string> GetFields();
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CleanArchitecture.Application.Queries.Tenants.GetTenantById;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using MockQueryable.NSubstitute;
|
||||
using NSubstitute;
|
||||
|
||||
namespace CleanArchitecture.Application.Tests.Fixtures.Queries.Tenants;
|
||||
@ -30,9 +28,11 @@ public sealed class GetTenantByIdTestFixture : QueryHandlerBaseFixture
|
||||
{
|
||||
tenant.Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
TenantRepository.GetByIdAsync(Arg.Is<Guid>(y => y == tenant.Id)).Returns(tenant);
|
||||
}
|
||||
|
||||
var tenantList = new List<Tenant> { tenant }.BuildMock();
|
||||
TenantRepository.GetAllNoTracking().Returns(tenantList);
|
||||
|
||||
return tenant;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ public sealed class GetUserByIdTestFixture : QueryHandlerBaseFixture
|
||||
|
||||
var query = new[] { user }.BuildMock();
|
||||
|
||||
UserRepository.GetAllNoTracking().Returns(query);
|
||||
UserRepository.GetByIdAsync(Arg.Is<Guid>(y => y == ExistingUserId)).Returns(user);
|
||||
}
|
||||
|
||||
public void SetupDeletedUserAsync()
|
||||
|
@ -32,6 +32,7 @@ public sealed class GetAllTenantsQueryHandler :
|
||||
{
|
||||
var tenantsQuery = _tenantRepository
|
||||
.GetAllNoTracking()
|
||||
.IgnoreQueryFilters()
|
||||
.Include(x => x.Users)
|
||||
.Where(x => request.IncludeDeleted || !x.Deleted);
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.ViewModels.Tenants;
|
||||
@ -7,7 +6,6 @@ using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CleanArchitecture.Application.Queries.Tenants.GetTenantById;
|
||||
|
||||
@ -25,10 +23,8 @@ public sealed class GetTenantByIdQueryHandler :
|
||||
|
||||
public async Task<TenantViewModel?> Handle(GetTenantByIdQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenant = _tenantRepository
|
||||
.GetAllNoTracking()
|
||||
.Include(x => x.Users)
|
||||
.FirstOrDefault(x => x.Id == request.TenantId && !x.Deleted);
|
||||
var tenant = await _tenantRepository
|
||||
.GetByIdAsync(request.TenantId);
|
||||
|
||||
if (tenant is null)
|
||||
{
|
||||
|
@ -32,6 +32,7 @@ public sealed class GetAllUsersQueryHandler :
|
||||
{
|
||||
var usersQuery = _userRepository
|
||||
.GetAllNoTracking()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(x => request.IncludeDeleted || !x.Deleted);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.SearchTerm))
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.ViewModels.Users;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
@ -24,9 +23,7 @@ public sealed class GetUserByIdQueryHandler :
|
||||
|
||||
public async Task<UserViewModel?> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = _userRepository
|
||||
.GetAllNoTracking()
|
||||
.FirstOrDefault(x => x.Id == request.UserId && !x.Deleted);
|
||||
var user = await _userRepository.GetByIdAsync(request.UserId);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
|
@ -34,6 +34,7 @@ public sealed class TenantsApiImplementation : TenantsApi.TenantsApiBase
|
||||
|
||||
var tenants = await _tenantRepository
|
||||
.GetAllNoTracking()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(tenant => idsAsGuids.Contains(tenant.Id))
|
||||
.Select(tenant => new Tenant
|
||||
{
|
||||
|
@ -34,6 +34,7 @@ public sealed class UsersApiImplementation : UsersApi.UsersApiBase
|
||||
|
||||
var users = await _userRepository
|
||||
.GetAllNoTracking()
|
||||
.IgnoreQueryFilters()
|
||||
.Where(user => idsAsGuids.Contains(user.Id))
|
||||
.Select(user => new GrpcUser
|
||||
{
|
||||
|
@ -12,7 +12,7 @@ public sealed class CreateTenantCommandHandlerTests
|
||||
private readonly CreateTenantCommandTestFixture _fixture = new();
|
||||
|
||||
[Fact]
|
||||
public async Task Should_Create_Tenant()
|
||||
public async Task Should_Create_Tenant()
|
||||
{
|
||||
var command = new CreateTenantCommand(
|
||||
Guid.NewGuid(),
|
||||
@ -29,7 +29,7 @@ public sealed class CreateTenantCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Should_Not_Create_Tenant_Insufficient_Permissions()
|
||||
public async Task Should_Not_Create_Tenant_Insufficient_Permissions()
|
||||
{
|
||||
_fixture.SetupUser();
|
||||
|
||||
@ -49,7 +49,7 @@ public sealed class CreateTenantCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Should_Not_Create_Tenant_Already_Exists()
|
||||
public async Task Should_Not_Create_Tenant_Already_Exists()
|
||||
{
|
||||
var command = new CreateTenantCommand(
|
||||
Guid.NewGuid(),
|
||||
|
@ -27,7 +27,7 @@ public sealed class DeleteUserCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Should_Not_Delete_Non_Existing_User()
|
||||
public async Task Should_Not_Delete_Non_Existing_User()
|
||||
{
|
||||
_fixture.SetupUser();
|
||||
|
||||
@ -45,7 +45,7 @@ public sealed class DeleteUserCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Should_Not_Delete_User_Insufficient_Permissions()
|
||||
public async Task Should_Not_Delete_User_Insufficient_Permissions()
|
||||
{
|
||||
var user = _fixture.SetupUser();
|
||||
|
||||
|
@ -52,7 +52,8 @@ public sealed class RabbitMqHandler : BackgroundService
|
||||
{
|
||||
if (!_configuration.Enabled)
|
||||
{
|
||||
_logger.LogInformation("RabbitMQ is disabled. Skipping the creation of exchange {exchangeName}.", exchangeName);
|
||||
_logger.LogInformation("RabbitMQ is disabled. Skipping the creation of exchange {exchangeName}.",
|
||||
exchangeName);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
using System.Linq;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
using CleanArchitecture.Infrastructure.Configurations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Database;
|
||||
|
||||
public class ApplicationDbContext : DbContext
|
||||
public partial class ApplicationDbContext : DbContext
|
||||
{
|
||||
public DbSet<User> Users { get; set; } = null!;
|
||||
public DbSet<Tenant> Tenants { get; set; } = null!;
|
||||
@ -14,6 +15,29 @@ public class ApplicationDbContext : DbContext
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
foreach (var entity in builder.Model.GetEntityTypes())
|
||||
{
|
||||
if (entity.ClrType.GetProperty(DbContextUtility.IsDeletedProperty) is not null)
|
||||
{
|
||||
builder.Entity(entity.ClrType)
|
||||
.HasQueryFilter(DbContextUtility.GetIsDeletedRestriction(entity.ClrType));
|
||||
}
|
||||
}
|
||||
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
ApplyConfigurations(builder);
|
||||
|
||||
// Make referential delete behaviour restrict instead of cascade for everything
|
||||
foreach (var relationship in builder.Model.GetEntityTypes()
|
||||
.SelectMany(x => x.GetForeignKeys()))
|
||||
{
|
||||
relationship.DeleteBehavior = DeleteBehavior.Restrict;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyConfigurations(ModelBuilder builder)
|
||||
{
|
||||
builder.ApplyConfiguration(new UserConfiguration());
|
||||
builder.ApplyConfiguration(new TenantConfiguration());
|
||||
|
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Database;
|
||||
|
||||
public partial class ApplicationDbContext
|
||||
{
|
||||
public static class DbContextUtility
|
||||
{
|
||||
public const string IsDeletedProperty = "Deleted";
|
||||
|
||||
public static readonly MethodInfo PropertyMethod = typeof(EF)
|
||||
.GetMethod(nameof(EF.Property), BindingFlags.Static | BindingFlags.Public)
|
||||
!.MakeGenericMethod(typeof(bool));
|
||||
|
||||
public static LambdaExpression GetIsDeletedRestriction(Type type)
|
||||
{
|
||||
var parm = Expression.Parameter(type, "it");
|
||||
var prop = Expression.Call(PropertyMethod, parm, Expression.Constant(IsDeletedProperty));
|
||||
var condition = Expression.MakeBinary(ExpressionType.Equal, prop, Expression.Constant(false));
|
||||
var lambda = Expression.Lambda(condition, parm);
|
||||
return lambda;
|
||||
}
|
||||
}
|
||||
}
|
@ -37,6 +37,8 @@ public sealed class TenantControllerTests : IClassFixture<TenantTestFixture>
|
||||
|
||||
message!.Data!.Id.Should().Be(_fixture.CreatedTenantId);
|
||||
message.Data.Name.Should().Be("Test Tenant");
|
||||
|
||||
message.Data.Users.Count().Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -55,6 +57,10 @@ public sealed class TenantControllerTests : IClassFixture<TenantTestFixture>
|
||||
message.Data!.Items
|
||||
.FirstOrDefault(x => x.Id == _fixture.CreatedTenantId)
|
||||
.Should().NotBeNull();
|
||||
|
||||
message.Data.Items
|
||||
.FirstOrDefault(x => x.Id == _fixture.CreatedTenantId)!
|
||||
.Users.Count().Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
using CleanArchitecture.Domain.Enums;
|
||||
using CleanArchitecture.Infrastructure.Database;
|
||||
|
||||
namespace CleanArchitecture.IntegrationTests.Fixtures;
|
||||
@ -16,6 +17,15 @@ public sealed class TenantTestFixture : TestFixtureBase
|
||||
CreatedTenantId,
|
||||
"Test Tenant"));
|
||||
|
||||
context.Users.Add(new User(
|
||||
Guid.NewGuid(),
|
||||
CreatedTenantId,
|
||||
"test@user.de",
|
||||
"test",
|
||||
"user",
|
||||
"Test User",
|
||||
UserRole.User));
|
||||
|
||||
context.SaveChanges();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user