mirror of
https://github.com/alex289/CleanArchitecture.git
synced 2025-08-22 19:28:34 +00:00
Merge branch 'main' into feature/dotnet_8
This commit is contained in:
commit
ca81e2f076
2
.github/workflows/dependency-updates.yml
vendored
2
.github/workflows/dependency-updates.yml
vendored
@ -53,7 +53,7 @@ jobs:
|
||||
branch: "update/${{ env.date }}"
|
||||
labels: dependencies
|
||||
delete-branch: true
|
||||
commit-message: "Update packages"
|
||||
commit-message: "chore: Update dependencies"
|
||||
assignees: ${{ env.assignee }}
|
||||
base: ${{ env.baseBranch }}
|
||||
title: "Automatic Package Update ${{ env.date }}"
|
||||
|
50
.github/workflows/dotnet.yml
vendored
50
.github/workflows/dotnet.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@ -7,25 +7,59 @@ on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE: alexdev28/clean-architecture
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
solutionFile: CleanArchitecture.sln
|
||||
projectName: CleanArchitecture
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Dependency Review
|
||||
uses: actions/dependency-review-action@v3
|
||||
with:
|
||||
base-ref: ${{ github.event.pull_request.base.sha || 'main' }}
|
||||
head-ref: ${{ github.event.pull_request.head.sha || github.ref }}
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 8.x.x
|
||||
dotnet-version: |
|
||||
8.x.x
|
||||
# 6.x.x
|
||||
# - name: Check for vulnerabilities
|
||||
# run: |
|
||||
# VERSION=$(curl -s https://jeremylong.github.io/DependencyCheck/current.txt)
|
||||
# curl -Ls "https://github.com/jeremylong/DependencyCheck/releases/download/v$VERSION/dependency-check-$VERSION-release.zip" --output dependency-check.zip
|
||||
# unzip dependency-check.zip
|
||||
# ./dependency-check/bin/dependency-check.sh -s "**/*.csproj" --project "${{ env.projectName }}" --failOnCVSS "7"
|
||||
# - name: Check for license issues
|
||||
# run: |
|
||||
# dotnet new tool-manifest
|
||||
# dotnet tool install --local liz.tool
|
||||
# dotnet liz "${{ env.solutionFile }}" --suppress-progressbar
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
# platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ env.DOCKER_IMAGE }}:latest
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ riderModule.iml
|
||||
.idea
|
||||
.vs
|
||||
CleanArchitecture.sln.DotSettings.user
|
||||
.DS_Store
|
@ -9,18 +9,18 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCore.HealthChecks.ApplicationStatus" Version="7.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Rabbitmq" Version="7.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="7.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Redis" Version="7.0.1" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="7.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="7.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.10">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.13">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="7.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.13" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -8,12 +8,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2"/>
|
||||
<PackageReference Include="MockQueryable.NSubstitute" Version="7.0.0"/>
|
||||
<PackageReference Include="NSubstitute" Version="5.0.0"/>
|
||||
<PackageReference Include="xunit" Version="2.5.0"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageReference Include="MockQueryable.NSubstitute" Version="7.0.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
@ -24,8 +24,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" />
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -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()
|
||||
|
@ -6,12 +6,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.10"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.13" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Proto\CleanArchitecture.Proto.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
|
||||
<ProjectReference Include="..\CleanArchitecture.Proto\CleanArchitecture.Proto.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
@ -32,7 +32,8 @@ public sealed class GetAllTenantsQueryHandler :
|
||||
{
|
||||
var tenantsQuery = _tenantRepository
|
||||
.GetAllNoTracking()
|
||||
.Include(x => x.Users)
|
||||
.IgnoreQueryFilters()
|
||||
.Include(x => x.Users.Where(y => request.IncludeDeleted || !y.Deleted))
|
||||
.Where(x => request.IncludeDeleted || !x.Deleted);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.SearchTerm))
|
||||
|
@ -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,7 @@ 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
|
||||
{
|
||||
|
@ -8,12 +8,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3"/>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2"/>
|
||||
<PackageReference Include="NSubstitute" Version="5.0.0"/>
|
||||
<PackageReference Include="xunit" Version="2.5.0"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
@ -24,7 +24,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Commands.Tenants.CreateTenant;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Shared.Events.Tenant;
|
||||
@ -11,13 +12,13 @@ public sealed class CreateTenantCommandHandlerTests
|
||||
private readonly CreateTenantCommandTestFixture _fixture = new();
|
||||
|
||||
[Fact]
|
||||
public void Should_Create_Tenant()
|
||||
public async Task Should_Create_Tenant()
|
||||
{
|
||||
var command = new CreateTenantCommand(
|
||||
Guid.NewGuid(),
|
||||
"Test Tenant");
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoDomainNotification()
|
||||
@ -28,7 +29,7 @@ public sealed class CreateTenantCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Create_Tenant_Insufficient_Permissions()
|
||||
public async Task Should_Not_Create_Tenant_Insufficient_Permissions()
|
||||
{
|
||||
_fixture.SetupUser();
|
||||
|
||||
@ -36,7 +37,7 @@ public sealed class CreateTenantCommandHandlerTests
|
||||
Guid.NewGuid(),
|
||||
"Test Tenant");
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
@ -48,7 +49,7 @@ public sealed class CreateTenantCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Create_Tenant_Already_Exists()
|
||||
public async Task Should_Not_Create_Tenant_Already_Exists()
|
||||
{
|
||||
var command = new CreateTenantCommand(
|
||||
Guid.NewGuid(),
|
||||
@ -56,7 +57,7 @@ public sealed class CreateTenantCommandHandlerTests
|
||||
|
||||
_fixture.SetupExistingTenant(command.AggregateId);
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Commands.Tenants.DeleteTenant;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Shared.Events.Tenant;
|
||||
@ -11,13 +12,13 @@ public sealed class DeleteTenantCommandHandlerTests
|
||||
private readonly DeleteTenantCommandTestFixture _fixture = new();
|
||||
|
||||
[Fact]
|
||||
public void Should_Delete_Tenant()
|
||||
public async Task Should_Delete_Tenant()
|
||||
{
|
||||
var tenant = _fixture.SetupTenant();
|
||||
|
||||
var command = new DeleteTenantCommand(tenant.Id);
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoDomainNotification()
|
||||
@ -26,13 +27,13 @@ public sealed class DeleteTenantCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Delete_Non_Existing_Tenant()
|
||||
public async Task Should_Not_Delete_Non_Existing_Tenant()
|
||||
{
|
||||
_fixture.SetupTenant();
|
||||
|
||||
var command = new DeleteTenantCommand(Guid.NewGuid());
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
@ -44,14 +45,14 @@ public sealed class DeleteTenantCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Delete_Tenant_Insufficient_Permissions()
|
||||
public async Task Should_Not_Delete_Tenant_Insufficient_Permissions()
|
||||
{
|
||||
var tenant = _fixture.SetupTenant();
|
||||
_fixture.SetupUser();
|
||||
|
||||
var command = new DeleteTenantCommand(tenant.Id);
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Commands.Tenants.UpdateTenant;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Shared.Events.Tenant;
|
||||
@ -11,7 +12,7 @@ public sealed class UpdateTenantCommandHandlerTests
|
||||
private readonly UpdateTenantCommandTestFixture _fixture = new();
|
||||
|
||||
[Fact]
|
||||
public void Should_Update_Tenant()
|
||||
public async Task Should_Update_Tenant()
|
||||
{
|
||||
var command = new UpdateTenantCommand(
|
||||
Guid.NewGuid(),
|
||||
@ -19,7 +20,7 @@ public sealed class UpdateTenantCommandHandlerTests
|
||||
|
||||
_fixture.SetupExistingTenant(command.AggregateId);
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyCommit()
|
||||
@ -30,7 +31,7 @@ public sealed class UpdateTenantCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Update_Tenant_Insufficient_Permissions()
|
||||
public async Task Should_Not_Update_Tenant_Insufficient_Permissions()
|
||||
{
|
||||
var command = new UpdateTenantCommand(
|
||||
Guid.NewGuid(),
|
||||
@ -38,7 +39,7 @@ public sealed class UpdateTenantCommandHandlerTests
|
||||
|
||||
_fixture.SetupUser();
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
@ -50,13 +51,13 @@ public sealed class UpdateTenantCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Update_Tenant_Not_Existing()
|
||||
public async Task Should_Not_Update_Tenant_Not_Existing()
|
||||
{
|
||||
var command = new UpdateTenantCommand(
|
||||
Guid.NewGuid(),
|
||||
"Tenant Name");
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Commands.Users.CreateUser;
|
||||
using CleanArchitecture.Domain.Enums;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
@ -13,7 +14,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
private readonly CreateUserCommandTestFixture _fixture = new();
|
||||
|
||||
[Fact]
|
||||
public void Should_Create_User()
|
||||
public async Task Should_Create_User()
|
||||
{
|
||||
_fixture.SetupCurrentUser();
|
||||
|
||||
@ -28,7 +29,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
"Email",
|
||||
"Po=PF]PC6t.?8?ks)A6W");
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoDomainNotification()
|
||||
@ -37,7 +38,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Create_Already_Existing_User()
|
||||
public async Task Should_Not_Create_Already_Existing_User()
|
||||
{
|
||||
_fixture.SetupCurrentUser();
|
||||
|
||||
@ -51,7 +52,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
"Email",
|
||||
"Po=PF]PC6t.?8?ks)A6W");
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
@ -63,7 +64,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Create_Already_Existing_Email()
|
||||
public async Task Should_Not_Create_Already_Existing_Email()
|
||||
{
|
||||
_fixture.SetupCurrentUser();
|
||||
|
||||
@ -86,7 +87,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
"Email",
|
||||
"Po=PF]PC6t.?8?ks)A6W");
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
@ -98,7 +99,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Create_User_Tenant_Does_Not_Exist()
|
||||
public async Task Should_Not_Create_User_Tenant_Does_Not_Exist()
|
||||
{
|
||||
_fixture.SetupCurrentUser();
|
||||
|
||||
@ -112,7 +113,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
"Email",
|
||||
"Po=PF]PC6t.?8?ks)A6W");
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
@ -124,7 +125,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Create_User_Insufficient_Permissions()
|
||||
public async Task Should_Not_Create_User_Insufficient_Permissions()
|
||||
{
|
||||
_fixture.SetupUser();
|
||||
|
||||
@ -136,7 +137,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
"Email",
|
||||
"Po=PF]PC6t.?8?ks)A6W");
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Shared.Events.User;
|
||||
@ -11,13 +12,13 @@ public sealed class DeleteUserCommandHandlerTests
|
||||
private readonly DeleteUserCommandTestFixture _fixture = new();
|
||||
|
||||
[Fact]
|
||||
public void Should_Delete_User()
|
||||
public async Task Should_Delete_User()
|
||||
{
|
||||
var user = _fixture.SetupUser();
|
||||
|
||||
var command = new DeleteUserCommand(user.Id);
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoDomainNotification()
|
||||
@ -26,13 +27,13 @@ public sealed class DeleteUserCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Delete_Non_Existing_User()
|
||||
public async Task Should_Not_Delete_Non_Existing_User()
|
||||
{
|
||||
_fixture.SetupUser();
|
||||
|
||||
var command = new DeleteUserCommand(Guid.NewGuid());
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
@ -44,7 +45,7 @@ public sealed class DeleteUserCommandHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Not_Delete_User_Insufficient_Permissions()
|
||||
public async Task Should_Not_Delete_User_Insufficient_Permissions()
|
||||
{
|
||||
var user = _fixture.SetupUser();
|
||||
|
||||
@ -52,7 +53,7 @@ public sealed class DeleteUserCommandHandlerTests
|
||||
|
||||
var command = new DeleteUserCommand(user.Id);
|
||||
|
||||
_fixture.CommandHandler.Handle(command, default).Wait();
|
||||
await _fixture.CommandHandler.Handle(command, default);
|
||||
|
||||
_fixture
|
||||
.VerifyNoCommit()
|
||||
|
@ -6,20 +6,20 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3"/>
|
||||
<PackageReference Include="FluentValidation" Version="11.7.1"/>
|
||||
<PackageReference Include="MediatR" Version="12.1.1"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1"/>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.5.0"/>
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.32.1"/>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="FluentValidation" Version="11.8.0" />
|
||||
<PackageReference Include="MediatR" Version="12.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.6.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Shared\CleanArchitecture.Shared.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Shared\CleanArchitecture.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -20,7 +20,7 @@ namespace CleanArchitecture.Domain.Commands.Users.LoginUser;
|
||||
public sealed class LoginUserCommandHandler : CommandHandlerBase,
|
||||
IRequestHandler<LoginUserCommand, string>
|
||||
{
|
||||
private const double _expiryDurationMinutes = 30;
|
||||
private const double _expiryDurationMinutes = 60;
|
||||
private readonly TokenSettings _tokenSettings;
|
||||
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -8,11 +8,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2"/>
|
||||
<PackageReference Include="NSubstitute" Version="5.0.0"/>
|
||||
<PackageReference Include="xunit" Version="2.5.0"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
@ -23,7 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -6,20 +6,20 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Shared\CleanArchitecture.Shared.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
|
||||
<ProjectReference Include="..\CleanArchitecture.Shared\CleanArchitecture.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="12.1.1"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.10"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.10"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.10"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.10">
|
||||
<PackageReference Include="MediatR" Version="12.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.13">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
138
CleanArchitecture.Infrastructure/Migrations/20231001125849_DeletedQueryFilter.Designer.cs
generated
Normal file
138
CleanArchitecture.Infrastructure/Migrations/20231001125849_DeletedQueryFilter.Designer.cs
generated
Normal file
@ -0,0 +1,138 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CleanArchitecture.Infrastructure.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20231001125849_DeletedQueryFilter")]
|
||||
partial class DeletedQueryFilter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.11")
|
||||
.HasAnnotation("Proxies:ChangeTracking", false)
|
||||
.HasAnnotation("Proxies:CheckEquality", false)
|
||||
.HasAnnotation("Proxies:LazyLoading", true)
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("CleanArchitecture.Domain.Entities.Tenant", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("Deleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("nvarchar(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Tenants");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("b542bf25-134c-47a2-a0df-84ed14d03c4a"),
|
||||
Deleted = false,
|
||||
Name = "Admin Tenant"
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CleanArchitecture.Domain.Entities.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<bool>("Deleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(320)
|
||||
.HasColumnType("nvarchar(320)");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTimeOffset?>("LastLoggedinDate")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("Users");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("7e3892c0-9374-49fa-a3fd-53db637a40ae"),
|
||||
Deleted = false,
|
||||
Email = "admin@email.com",
|
||||
FirstName = "Admin",
|
||||
LastName = "User",
|
||||
Password = "$2a$12$Blal/uiFIJdYsCLTMUik/egLbfg3XhbnxBC6Sb5IKz2ZYhiU/MzL2",
|
||||
Role = 0,
|
||||
Status = 0,
|
||||
TenantId = new Guid("b542bf25-134c-47a2-a0df-84ed14d03c4a")
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CleanArchitecture.Domain.Entities.User", b =>
|
||||
{
|
||||
b.HasOne("CleanArchitecture.Domain.Entities.Tenant", "Tenant")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CleanArchitecture.Domain.Entities.Tenant", b =>
|
||||
{
|
||||
b.Navigation("Users");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class DeletedQueryFilter : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Users_Tenants_TenantId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Users_Tenants_TenantId",
|
||||
table: "Users",
|
||||
column: "TenantId",
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Users_Tenants_TenantId",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Users_Tenants_TenantId",
|
||||
table: "Users",
|
||||
column: "TenantId",
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ namespace CleanArchitecture.Infrastructure.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.10")
|
||||
.HasAnnotation("ProductVersion", "7.0.11")
|
||||
.HasAnnotation("Proxies:ChangeTracking", false)
|
||||
.HasAnnotation("Proxies:CheckEquality", false)
|
||||
.HasAnnotation("Proxies:LazyLoading", true)
|
||||
@ -119,7 +119,7 @@ namespace CleanArchitecture.Infrastructure.Migrations
|
||||
b.HasOne("CleanArchitecture.Domain.Entities.Tenant", "Tenant")
|
||||
.WithMany("Users")
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Tenant");
|
||||
|
@ -8,15 +8,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.10"/>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.10"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.10"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.10"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2"/>
|
||||
<PackageReference Include="xunit" Version="2.5.0"/>
|
||||
<PackageReference Include="Xunit.Priority" Version="1.1.6"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageReference Include="xunit" Version="2.6.0" />
|
||||
<PackageReference Include="Xunit.Priority" Version="1.1.6" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
@ -27,8 +27,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Api\CleanArchitecture.Api.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Api\CleanArchitecture.Api.csproj" />
|
||||
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -6,16 +6,16 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="Users\Models.proto" GrpcServices="Both"/>
|
||||
<Protobuf Include="Users\UsersApi.proto" GrpcServices="Both"/>
|
||||
<Protobuf Include="Tenants\Models.proto" GrpcServices="Both"/>
|
||||
<Protobuf Include="Tenants\TenantsApi.proto" GrpcServices="Both"/>
|
||||
<Protobuf Include="Users\Models.proto" GrpcServices="Both" />
|
||||
<Protobuf Include="Users\UsersApi.proto" GrpcServices="Both" />
|
||||
<Protobuf Include="Tenants\Models.proto" GrpcServices="Both" />
|
||||
<Protobuf Include="Tenants\TenantsApi.proto" GrpcServices="Both" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.24.2"/>
|
||||
<PackageReference Include="Google.Protobuf.Tools" Version="3.24.2"/>
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.56.0"/>
|
||||
<PackageReference Include="Google.Protobuf" Version="3.24.4" />
|
||||
<PackageReference Include="Google.Protobuf.Tools" Version="3.24.4" />
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.58.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -8,12 +8,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2"/>
|
||||
<PackageReference Include="MockQueryable.NSubstitute" Version="7.0.0"/>
|
||||
<PackageReference Include="NSubstitute" Version="5.0.0"/>
|
||||
<PackageReference Include="xunit" Version="2.5.0"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageReference Include="MockQueryable.NSubstitute" Version="7.0.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
@ -24,9 +24,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj"/>
|
||||
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" />
|
||||
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
|
||||
<ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
10
Readme.md
10
Readme.md
@ -57,15 +57,13 @@ Running the container
|
||||
|
||||
### Using docker-compose
|
||||
|
||||
1. Change the ConnectionString in the appsettings.json to `Server=db;Database=clean-architecture;Trusted_Connection=False;MultipleActiveResultSets=true;TrustServerCertificate=True;User Id=SA;Password=Password123!#`
|
||||
2. Build the Dockerfile: `docker build -t clean-architecture .`
|
||||
3. Running the docker compose: `docker-compose up -d` (Delete: `docker-compose down`)
|
||||
1. Build the Dockerfile: `docker build -t clean-architecture .`
|
||||
2. Running the docker compose: `docker-compose up -d` (Delete: `docker-compose down`)
|
||||
|
||||
### Using Kubernetes
|
||||
|
||||
1. Change the ConnectionString in the appsettings.json to `Server=sql-server;Database=clean-architecture;Trusted_Connection=False;MultipleActiveResultSets=true;TrustServerCertificate=True;User Id=SA;Password=Password123!#`
|
||||
2. Build the docker image and push it to the docker hub (Change the image name in the `k8s-deployment.yml` to your own)
|
||||
Apply the deployment file: `kubectl apply -f k8s-deployment.yml` (Delete: `kubectl delete -f k8s-deployment.yml`)
|
||||
1. Build the docker image and push it to the docker hub (Change the image name in the `k8s-deployment.yml` to your own)
|
||||
2. Apply the deployment file: `kubectl apply -f k8s-deployment.yml` (Delete: `kubectl delete -f k8s-deployment.yml`)
|
||||
|
||||
|
||||
## Running the Tests
|
||||
|
@ -13,6 +13,8 @@ services:
|
||||
condition: service_healthy
|
||||
links:
|
||||
- db
|
||||
environment:
|
||||
- ConnectionStrings__DefaultConnection=Server=db;Database=clean-architecture;Trusted_Connection=False;MultipleActiveResultSets=true;TrustServerCertificate=True;User Id=SA;Password=Password123!#
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "--fail", "http://localhost/healthz"]
|
||||
interval: 30s
|
||||
|
@ -31,4 +31,7 @@ spec:
|
||||
# Replace this with the path to your built image
|
||||
image: alexdev28/clean-architecture:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- containerPort: 80
|
||||
env:
|
||||
- name: ConnectionStrings__DefaultConnection
|
||||
value: Server=sql-server;Database=clean-architecture;Trusted_Connection=False;MultipleActiveResultSets=true;TrustServerCertificate=True;User Id=SA;Password=Password123!#
|
Loading…
Reference in New Issue
Block a user