Skip to content

Commit 4ec0617

Browse files
committed
better cqrs structure
1 parent 1dd6778 commit 4ec0617

25 files changed

+341
-84
lines changed

src/Directory.Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.2" />
3232
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.2" />
3333
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.2" />
34+
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.8.0" />
3435
<PackageVersion Include="Microsoft.NET.Build.Containers" Version="8.0.100" />
3536
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
3637
<PackageVersion Include="MimeKit" Version="4.10.0" />
@@ -49,6 +50,7 @@
4950
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0" />
5051
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.12.0" />
5152
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.12.0" />
53+
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.8.0" />
5254
</ItemGroup>
5355
<ItemGroup>
5456
<PackageVersion Include="Mapster" Version="7.4.0" />
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<RootNamespace>FSH.Framework.Auditing.Contracts</RootNamespace>
4+
<AssemblyName>FSH.Framework.Auditing.Contracts</AssemblyName>
5+
</PropertyGroup>
6+
<PropertyGroup>
7+
<TargetFramework>net9.0</TargetFramework>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
<Nullable>enable</Nullable>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\..\Core\Core.csproj" />
14+
</ItemGroup>
15+
16+
</Project>

src/framework/Auditing/Auditing.Core/Enums/AuditOperation.cs renamed to src/framework/Auditing/Auditing.Contracts/Enums/AuditOperation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace FSH.Framework.Auditing.Core.Enums;
1+
namespace FSH.Framework.Auditing.Contracts.Enums;
22
public enum AuditOperation
33
{
44
None = 0,

src/framework/Auditing/Auditing.Core/Events/AuditPublishedEvent.cs renamed to src/framework/Auditing/Auditing.Contracts/Events/AuditPublishedEvent.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
using FSH.Framework.Auditing.Core.Dtos;
2-
using FSH.Framework.Core.Messaging.Events;
1+
using FSH.Framework.Core.Messaging.Events;
32

4-
namespace FSH.Framework.Auditing.Core.Events;
3+
namespace FSH.Framework.Auditing.Contracts.Events;
54

65
public class AuditPublishedEvent : INotification
76
{

src/framework/Auditing/Auditing.Core/Dtos/Trail.cs renamed to src/framework/Auditing/Auditing.Contracts/Trail.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
using System.Collections.ObjectModel;
22
using System.ComponentModel.DataAnnotations.Schema;
33
using System.Text.Json;
4-
using FSH.Framework.Auditing.Core.Enums;
4+
using FSH.Framework.Auditing.Contracts.Enums;
55

6-
namespace FSH.Framework.Auditing.Core.Dtos;
6+
namespace FSH.Framework.Auditing.Contracts;
77

88
public class Trail
99
{
1010
public Guid Id { get; set; }
1111
public Guid UserId { get; set; }
1212
public DateTimeOffset DateTime { get; set; }
1313
public AuditOperation Operation { get; set; } // e.g., "Create", "Update", "Delete"
14+
public required string Description { get; set; }
1415
public string? EntityName { get; set; } // Name of the entity/table affected
1516

1617
// Store dictionaries as JSON in the database

src/framework/Auditing/Auditing.Core/Abstractions/IAuditService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using FSH.Framework.Auditing.Core.Dtos;
1+
using FSH.Framework.Auditing.Contracts;
22

33
namespace FSH.Framework.Auditing.Core.Abstractions;
44
public interface IAuditService

src/framework/Auditing/Auditing.Core/Abstractions/IAuditingDbContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using FSH.Framework.Auditing.Core.Dtos;
1+
using FSH.Framework.Auditing.Contracts;
22
using Microsoft.EntityFrameworkCore;
33

44
namespace FSH.Framework.Auditing.Core.Abstractions;

src/framework/Auditing/Auditing.Core/Auditing.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
</ItemGroup>
1414
<ItemGroup>
1515
<ProjectReference Include="..\..\Core\Core.csproj" />
16+
<ProjectReference Include="..\Auditing.Contracts\Auditing.Contracts.csproj" />
1617
</ItemGroup>
1718

1819
</Project>

src/framework/Auditing/Auditing.Endpoints/AuditingModule.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Reflection;
22
using Asp.Versioning;
33
using Asp.Versioning.Builder;
4-
using FSH.Framework.Auditing.Endpoints.v1;
4+
using FSH.Framework.Auditing.Endpoints.v1.GetUserTrails;
55
using FSH.Framework.Infrastructure.Messaging.CQRS;
66
using Microsoft.AspNetCore.Builder;
77
using Microsoft.AspNetCore.Http;
@@ -35,7 +35,7 @@ public static IEndpointRouteBuilder MapAuditingEndpoints(this IEndpointRouteBuil
3535
.WithApiVersionSet(apiVersionSet);
3636

3737
// v1 endpoints
38-
GetUserTrails.MapEndpoint(group);
38+
GetUserTrailsEndpoint.MapEndpoint(group);
3939

4040
return endpoints;
4141
}

src/framework/Auditing/Auditing.Endpoints/v1/GetUserTrails.cs

Lines changed: 0 additions & 46 deletions
This file was deleted.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using FSH.Framework.Core.Messaging.CQRS;
2+
using FSH.Framework.Shared.Authorization;
3+
using Microsoft.AspNetCore.Builder;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Routing;
6+
7+
namespace FSH.Framework.Auditing.Endpoints.v1.GetUserTrails;
8+
public static class GetUserTrailsEndpoint
9+
{
10+
public static RouteHandlerBuilder MapEndpoint(this IEndpointRouteBuilder endpoints)
11+
{
12+
return endpoints.MapGet("/users/{userId:guid}/trails", async (
13+
Guid userId,
14+
IQueryDispatcher dispatcher,
15+
CancellationToken cancellationToken) =>
16+
{
17+
var result = await dispatcher.SendAsync<GetUserTrailsQuery, GetUserTrailsResponse>(
18+
new GetUserTrailsQuery(userId), cancellationToken);
19+
20+
return TypedResults.Ok(result);
21+
})
22+
.WithName("GetUserTrails")
23+
.WithSummary("Get user's audit trail details")
24+
.WithDescription("Returns the audit trail details for a specific user.")
25+
.RequirePermission("Permissions.AuditTrails.View");
26+
}
27+
}
28+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using FSH.Framework.Auditing.Core.Abstractions;
2+
using FSH.Framework.Core.Messaging.CQRS;
3+
4+
namespace FSH.Framework.Auditing.Endpoints.v1.GetUserTrails;
5+
public sealed class GetUserTrailsHandler(IAuditService auditService)
6+
: IQueryHandler<GetUserTrailsQuery, GetUserTrailsResponse>
7+
{
8+
public async Task<GetUserTrailsResponse> HandleAsync(GetUserTrailsQuery query, CancellationToken cancellationToken = default)
9+
{
10+
var trails = await auditService.GetUserTrailsAsync(query.UserId);
11+
return new GetUserTrailsResponse(trails);
12+
}
13+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
using FSH.Framework.Core.Messaging.CQRS;
2+
3+
namespace FSH.Framework.Auditing.Endpoints.v1.GetUserTrails;
4+
public sealed record GetUserTrailsQuery(Guid UserId) : IQuery<GetUserTrailsResponse>;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
using FSH.Framework.Auditing.Contracts;
2+
3+
namespace FSH.Framework.Auditing.Endpoints.v1.GetUserTrails;
4+
public sealed record GetUserTrailsResponse(IReadOnlyList<Trail> AuditTrails);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace FSH.Framework.Auditing.Endpoints.v1.GetUserTrails;
2+
using FluentValidation;
3+
4+
public sealed class GetUserTrailsValidator : AbstractValidator<GetUserTrailsQuery>
5+
{
6+
public GetUserTrailsValidator()
7+
{
8+
RuleFor(x => x.UserId).NotEmpty();
9+
}
10+
}

src/framework/FSH.Framework.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tenant.Infrastructure", "Te
3737
EndProject
3838
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
3939
EndProject
40+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Auditing.Contracts", "Auditing\Auditing.Contracts\Auditing.Contracts.csproj", "{7A14CE7C-8D6A-4AF2-9F28-5E981C77DE55}"
41+
EndProject
4042
Global
4143
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4244
Debug|Any CPU = Debug|Any CPU
@@ -95,6 +97,10 @@ Global
9597
{F8AE32F3-31B9-4962-BE7A-1326BD13B739}.Debug|Any CPU.Build.0 = Debug|Any CPU
9698
{F8AE32F3-31B9-4962-BE7A-1326BD13B739}.Release|Any CPU.ActiveCfg = Release|Any CPU
9799
{F8AE32F3-31B9-4962-BE7A-1326BD13B739}.Release|Any CPU.Build.0 = Release|Any CPU
100+
{7A14CE7C-8D6A-4AF2-9F28-5E981C77DE55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
101+
{7A14CE7C-8D6A-4AF2-9F28-5E981C77DE55}.Debug|Any CPU.Build.0 = Debug|Any CPU
102+
{7A14CE7C-8D6A-4AF2-9F28-5E981C77DE55}.Release|Any CPU.ActiveCfg = Release|Any CPU
103+
{7A14CE7C-8D6A-4AF2-9F28-5E981C77DE55}.Release|Any CPU.Build.0 = Release|Any CPU
98104
EndGlobalSection
99105
GlobalSection(SolutionProperties) = preSolution
100106
HideSolutionNode = FALSE
@@ -112,6 +118,7 @@ Global
112118
{AD14C8C7-7A0A-40E1-88C9-3F535B89C7E6} = {24E922E3-8C91-43A1-A110-D8F5276566BA}
113119
{86BC889E-322A-460E-81B6-601EE65B37C1} = {24E922E3-8C91-43A1-A110-D8F5276566BA}
114120
{F8AE32F3-31B9-4962-BE7A-1326BD13B739} = {24E922E3-8C91-43A1-A110-D8F5276566BA}
121+
{7A14CE7C-8D6A-4AF2-9F28-5E981C77DE55} = {E5141F38-2D08-44DA-9A6C-B96890515168}
115122
EndGlobalSection
116123
GlobalSection(ExtensibilityGlobals) = postSolution
117124
SolutionGuid = {A2A6BABD-325C-4482-8830-41058E5D509D}

src/framework/Identity/Identity.Core/Tokens/ITokenService.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ public interface ITokenService
33
{
44
Task<TokenDto> GenerateTokenAsync(TokenGenerationRequest request, string ipAddress, CancellationToken cancellationToken);
55
Task<TokenDto> RefreshTokenAsync(TokenRefreshRequest request, string ipAddress, CancellationToken cancellationToken);
6-
76
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using FSH.Framework.Core.Messaging.CQRS;
2+
3+
namespace FSH.Framework.Identity.Endpoints.v1.Tokens.Generate;
4+
public sealed record TokenGenerationCommand(string Email, string Password)
5+
: ICommand<TokenGenerationResponse>;
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using FSH.Framework.Identity.Core.Tokens;
2-
using FSH.Framework.Shared.Extensions;
1+
using FSH.Framework.Core.Messaging.CQRS;
32
using Microsoft.AspNetCore.Builder;
43
using Microsoft.AspNetCore.Http;
54
using Microsoft.AspNetCore.Routing;
@@ -9,18 +8,19 @@ public static class TokenGenerationEndpoint
98
{
109
internal static RouteHandlerBuilder MapTokenGenerationEndpoint(this IEndpointRouteBuilder endpoints)
1110
{
12-
return endpoints.MapPost("/", (TokenGenerationRequest request,
11+
return endpoints.MapPost("/", async (
12+
TokenGenerationCommand command,
1313
string tenant,
14-
ITokenService service,
14+
ICommandDispatcher dispatcher,
1515
HttpContext context,
1616
CancellationToken cancellationToken) =>
1717
{
18-
string ip = context.GetIpAddress();
19-
return service.GenerateTokenAsync(request, ip!, cancellationToken);
18+
var result = await dispatcher.SendAsync<TokenGenerationCommand, TokenGenerationResponse>(command, cancellationToken);
19+
return TypedResults.Ok(result);
2020
})
21-
.WithName(nameof(TokenGenerationEndpoint))
22-
.WithSummary("generate JWTs")
23-
.WithDescription("generate JWTs")
21+
.WithName("TokenGeneration")
22+
.WithSummary("Generate JWTs")
23+
.WithDescription("Generates access and refresh tokens.")
2424
.AllowAnonymous();
2525
}
2626
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using FSH.Framework.Core.Messaging.CQRS;
2+
using FSH.Framework.Identity.Core.Tokens;
3+
using FSH.Framework.Shared.Extensions;
4+
using Microsoft.AspNetCore.Http;
5+
6+
namespace FSH.Framework.Identity.Endpoints.v1.Tokens.Generate;
7+
public sealed class TokenGenerationHandler(
8+
ITokenService tokenService,
9+
HttpContext context)
10+
: ICommandHandler<TokenGenerationCommand, TokenGenerationResponse>
11+
{
12+
public async Task<TokenGenerationResponse> HandleAsync(TokenGenerationCommand command, CancellationToken cancellationToken = default)
13+
{
14+
var request = new TokenGenerationRequest(command.Email, command.Password);
15+
string ip = context.GetIpAddress();
16+
17+
var token = await tokenService.GenerateTokenAsync(request, ip, cancellationToken);
18+
19+
return new TokenGenerationResponse(token.Token, token.RefreshToken, token.RefreshTokenExpiryTime);
20+
}
21+
}

src/framework/Identity/Identity.Endpoints/v1/Tokens/Generate/TokenGenerationRequestValidator.cs

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace FSH.Framework.Identity.Endpoints.v1.Tokens.Generate;
2+
public sealed record TokenGenerationResponse(
3+
string Token,
4+
string RefreshToken,
5+
DateTime RefreshTokenExpiryTime);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace FSH.Framework.Identity.Endpoints.v1.Tokens.Generate;
2+
using FluentValidation;
3+
4+
public sealed class TokenGenerationValidator : AbstractValidator<TokenGenerationCommand>
5+
{
6+
public TokenGenerationValidator()
7+
{
8+
RuleFor(p => p.Email)
9+
.Cascade(CascadeMode.Stop)
10+
.NotEmpty()
11+
.EmailAddress();
12+
13+
RuleFor(p => p.Password)
14+
.Cascade(CascadeMode.Stop)
15+
.NotEmpty();
16+
}
17+
}

src/framework/Identity/Identity.Infrastructure/Identity.Infrastructure.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<Nullable>enable</Nullable>
1010
</PropertyGroup>
1111
<ItemGroup>
12+
<ProjectReference Include="..\..\Auditing\Auditing.Contracts\Auditing.Contracts.csproj" />
1213
<ProjectReference Include="..\..\Core\Core.csproj" />
1314
<ProjectReference Include="..\..\Shared\Shared.csproj" />
1415
<ProjectReference Include="..\Identity.Core\Identity.Core.csproj" />
@@ -18,5 +19,7 @@
1819
</ItemGroup>
1920
<ItemGroup>
2021
<PackageReference Include="Finbuckle.MultiTenant.EntityFrameworkCore" />
22+
<PackageReference Include="Microsoft.IdentityModel.Tokens" />
23+
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
2124
</ItemGroup>
2225
</Project>

0 commit comments

Comments
 (0)