mirror of
https://github.com/alex289/CleanArchitecture.git
synced 2025-06-30 02:31:08 +00:00
feat: Add endpoints for tenants
This commit is contained in:
parent
64fb1067e0
commit
816d92fc85
75
CleanArchitecture.Api/Controllers/TenantController.cs
Normal file
75
CleanArchitecture.Api/Controllers/TenantController.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Api.Models;
|
||||
using CleanArchitecture.Application.Interfaces;
|
||||
using CleanArchitecture.Application.ViewModels.Tenants;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace CleanArchitecture.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("/api/v1/[controller]")]
|
||||
public sealed class TenantController : ApiController
|
||||
{
|
||||
private readonly ITenantService _tenantService;
|
||||
|
||||
public TenantController(
|
||||
INotificationHandler<DomainNotification> notifications,
|
||||
ITenantService tenantService) : base(notifications)
|
||||
{
|
||||
_tenantService = tenantService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[SwaggerOperation("Get a list of all tenants")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<IEnumerable<TenantViewModel>>))]
|
||||
public async Task<IActionResult> GetAllTenantsAsync()
|
||||
{
|
||||
var tenants = await _tenantService.GetAllTenantsAsync();
|
||||
return Response(tenants);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
[SwaggerOperation("Get a tenant by id")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<TenantViewModel>))]
|
||||
public async Task<IActionResult> GetTenantByIdAsync(
|
||||
[FromRoute] Guid id,
|
||||
[FromQuery] bool isDeleted = false)
|
||||
{
|
||||
var tenant = await _tenantService.GetTenantByIdAsync(id, isDeleted);
|
||||
return Response(tenant);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[SwaggerOperation("Create a new tenant")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))]
|
||||
public async Task<IActionResult> CreateTenantAsync([FromBody] CreateTenantViewModel tenant)
|
||||
{
|
||||
var tenantId = await _tenantService.CreateTenantAsync(tenant);
|
||||
return Response(tenantId);
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[SwaggerOperation("Update an existing tenant")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<UpdateTenantViewModel>))]
|
||||
public async Task<IActionResult> UpdateTenantAsync([FromBody] UpdateTenantViewModel tenant)
|
||||
{
|
||||
await _tenantService.UpdateTenantAsync(tenant);
|
||||
return Response(tenant);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
[SwaggerOperation("Delete an existing tenant")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))]
|
||||
public async Task<IActionResult> DeleteTenantAsync([FromRoute] Guid id)
|
||||
{
|
||||
await _tenantService.DeleteTenantAsync(id);
|
||||
return Response(id);
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ using Swashbuckle.AspNetCore.Annotations;
|
||||
namespace CleanArchitecture.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("/api/v1/[controller]")]
|
||||
public sealed class UserController : ApiController
|
||||
{
|
||||
@ -25,7 +26,6 @@ public sealed class UserController : ApiController
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet]
|
||||
[SwaggerOperation("Get a list of all users")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<IEnumerable<UserViewModel>>))]
|
||||
@ -35,7 +35,6 @@ public sealed class UserController : ApiController
|
||||
return Response(users);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("{id:guid}")]
|
||||
[SwaggerOperation("Get a user by id")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<UserViewModel>))]
|
||||
@ -47,7 +46,6 @@ public sealed class UserController : ApiController
|
||||
return Response(user);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("me")]
|
||||
[SwaggerOperation("Get the current active user")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<UserViewModel>))]
|
||||
@ -58,6 +56,7 @@ public sealed class UserController : ApiController
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[SwaggerOperation("Create a new user")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))]
|
||||
public async Task<IActionResult> CreateUserAsync([FromBody] CreateUserViewModel viewModel)
|
||||
@ -66,7 +65,6 @@ public sealed class UserController : ApiController
|
||||
return Response(userId);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpDelete("{id:guid}")]
|
||||
[SwaggerOperation("Delete a user")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))]
|
||||
@ -76,7 +74,6 @@ public sealed class UserController : ApiController
|
||||
return Response(id);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPut]
|
||||
[SwaggerOperation("Update a user")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<UpdateUserViewModel>))]
|
||||
@ -86,7 +83,6 @@ public sealed class UserController : ApiController
|
||||
return Response(viewModel);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost("changePassword")]
|
||||
[SwaggerOperation("Change a password for the current active user")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<ChangePasswordViewModel>))]
|
||||
@ -97,6 +93,7 @@ public sealed class UserController : ApiController
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
[AllowAnonymous]
|
||||
[SwaggerOperation("Get a signed token for a user")]
|
||||
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<string>))]
|
||||
public async Task<IActionResult> LoginUserAsync([FromBody] LoginUserViewModel viewModel)
|
||||
|
@ -14,4 +14,6 @@
|
||||
<ProjectReference Include="..\CleanArchitecture.Proto\CleanArchitecture.Proto.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
|
@ -1,8 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using CleanArchitecture.Application.Interfaces;
|
||||
using CleanArchitecture.Application.Queries.Tenants.GetAll;
|
||||
using CleanArchitecture.Application.Queries.Tenants.GetTenantById;
|
||||
using CleanArchitecture.Application.Queries.Users.GetAll;
|
||||
using CleanArchitecture.Application.Queries.Users.GetUserById;
|
||||
using CleanArchitecture.Application.Services;
|
||||
using CleanArchitecture.Application.ViewModels.Tenants;
|
||||
using CleanArchitecture.Application.ViewModels.Users;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -14,15 +17,21 @@ public static class ServiceCollectionExtension
|
||||
public static IServiceCollection AddServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IUserService, UserService>();
|
||||
services.AddScoped<ITenantService, TenantService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddQueryHandlers(this IServiceCollection services)
|
||||
{
|
||||
// User
|
||||
services.AddScoped<IRequestHandler<GetUserByIdQuery, UserViewModel?>, GetUserByIdQueryHandler>();
|
||||
services.AddScoped<IRequestHandler<GetAllUsersQuery, IEnumerable<UserViewModel>>, GetAllUsersQueryHandler>();
|
||||
|
||||
// Tenant
|
||||
services.AddScoped<IRequestHandler<GetTenantByIdQuery, TenantViewModel?>, GetTenantByIdQueryHandler>();
|
||||
services.AddScoped<IRequestHandler<GetAllTenantsQuery, IEnumerable<TenantViewModel>>, GetAllTenantsQueryHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
15
CleanArchitecture.Application/Interfaces/ITenantService.cs
Normal file
15
CleanArchitecture.Application/Interfaces/ITenantService.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.ViewModels.Tenants;
|
||||
|
||||
namespace CleanArchitecture.Application.Interfaces;
|
||||
|
||||
public interface ITenantService
|
||||
{
|
||||
public Task<Guid> CreateTenantAsync(CreateTenantViewModel tenant);
|
||||
public Task UpdateTenantAsync(UpdateTenantViewModel tenant);
|
||||
public Task DeleteTenantAsync(Guid tenantId);
|
||||
public Task<TenantViewModel?> GetTenantByIdAsync(Guid tenantId, bool deleted);
|
||||
public Task<IEnumerable<TenantViewModel>> GetAllTenantsAsync();
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using CleanArchitecture.Application.ViewModels.Tenants;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Application.Queries.Tenants.GetAll;
|
||||
|
||||
public sealed record GetAllTenantsQuery() : IRequest<IEnumerable<TenantViewModel>>;
|
@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.ViewModels.Tenants;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace CleanArchitecture.Application.Queries.Tenants.GetAll;
|
||||
|
||||
public sealed class GetAllTenantsQueryHandler :
|
||||
IRequestHandler<GetAllTenantsQuery, IEnumerable<TenantViewModel>>
|
||||
{
|
||||
private readonly ITenantRepository _tenantRepository;
|
||||
|
||||
public GetAllTenantsQueryHandler(ITenantRepository tenantRepository)
|
||||
{
|
||||
_tenantRepository = tenantRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TenantViewModel>> Handle(
|
||||
GetAllTenantsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await _tenantRepository
|
||||
.GetAllNoTracking()
|
||||
.Where(x => !x.Deleted)
|
||||
.Select(x => TenantViewModel.FromTenant(x))
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
using System;
|
||||
using CleanArchitecture.Application.ViewModels.Tenants;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Application.Queries.Tenants.GetTenantById;
|
||||
|
||||
public sealed record GetTenantByIdQuery(Guid TenantId, bool IsDeleted) : IRequest<TenantViewModel?>;
|
@ -0,0 +1,45 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.ViewModels.Tenants;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Application.Queries.Tenants.GetTenantById;
|
||||
|
||||
public sealed class GetTenantByIdQueryHandler :
|
||||
IRequestHandler<GetTenantByIdQuery, TenantViewModel?>
|
||||
{
|
||||
private readonly ITenantRepository _tenantRepository;
|
||||
private readonly IMediatorHandler _bus;
|
||||
|
||||
public GetTenantByIdQueryHandler(ITenantRepository tenantRepository, IMediatorHandler bus)
|
||||
{
|
||||
_tenantRepository = tenantRepository;
|
||||
_bus = bus;
|
||||
}
|
||||
|
||||
public async Task<TenantViewModel?> Handle(GetTenantByIdQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var tenant = _tenantRepository
|
||||
.GetAllNoTracking()
|
||||
.FirstOrDefault(x =>
|
||||
x.Id == request.TenantId &&
|
||||
x.Deleted == request.IsDeleted);
|
||||
|
||||
if (tenant is null)
|
||||
{
|
||||
await _bus.RaiseEventAsync(
|
||||
new DomainNotification(
|
||||
nameof(GetTenantByIdQuery),
|
||||
$"Tenant with id {request.TenantId} could not be found",
|
||||
ErrorCodes.ObjectNotFound));
|
||||
return null;
|
||||
}
|
||||
|
||||
return TenantViewModel.FromTenant(tenant);
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ public sealed class GetUserByIdQueryHandler :
|
||||
x.Id == request.UserId &&
|
||||
x.Deleted == request.IsDeleted);
|
||||
|
||||
if (user == null)
|
||||
if (user is null)
|
||||
{
|
||||
await _bus.RaiseEventAsync(
|
||||
new DomainNotification(
|
||||
|
56
CleanArchitecture.Application/Services/TenantService.cs
Normal file
56
CleanArchitecture.Application/Services/TenantService.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Application.Interfaces;
|
||||
using CleanArchitecture.Application.Queries.Tenants.GetAll;
|
||||
using CleanArchitecture.Application.Queries.Tenants.GetTenantById;
|
||||
using CleanArchitecture.Application.ViewModels.Tenants;
|
||||
using CleanArchitecture.Domain.Commands.Tenants.CreateTenant;
|
||||
using CleanArchitecture.Domain.Commands.Tenants.DeleteTenant;
|
||||
using CleanArchitecture.Domain.Commands.Tenants.UpdateTenant;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
|
||||
namespace CleanArchitecture.Application.Services;
|
||||
|
||||
public sealed class TenantService : ITenantService
|
||||
{
|
||||
private readonly IMediatorHandler _bus;
|
||||
|
||||
public TenantService(IMediatorHandler bus)
|
||||
{
|
||||
_bus = bus;
|
||||
}
|
||||
|
||||
public async Task<Guid> CreateTenantAsync(CreateTenantViewModel tenant)
|
||||
{
|
||||
var tenantId = Guid.NewGuid();
|
||||
|
||||
await _bus.SendCommandAsync(new CreateTenantCommand(
|
||||
tenantId,
|
||||
tenant.Name));
|
||||
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public async Task UpdateTenantAsync(UpdateTenantViewModel tenant)
|
||||
{
|
||||
await _bus.SendCommandAsync(new UpdateTenantCommand(
|
||||
tenant.Id,
|
||||
tenant.Name));
|
||||
}
|
||||
|
||||
public async Task DeleteTenantAsync(Guid tenantId)
|
||||
{
|
||||
await _bus.SendCommandAsync(new DeleteTenantCommand(tenantId));
|
||||
}
|
||||
|
||||
public async Task<TenantViewModel?> GetTenantByIdAsync(Guid tenantId, bool deleted)
|
||||
{
|
||||
return await _bus.QueryAsync(new GetTenantByIdQuery(tenantId, deleted));
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TenantViewModel>> GetAllTenantsAsync()
|
||||
{
|
||||
return await _bus.QueryAsync(new GetAllTenantsQuery());
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
namespace CleanArchitecture.Application.ViewModels.Tenants;
|
||||
|
||||
public sealed record CreateTenantViewModel(string Name);
|
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CleanArchitecture.Application.ViewModels.Users;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
|
||||
namespace CleanArchitecture.Application.ViewModels.Tenants;
|
||||
|
||||
public sealed class TenantViewModel
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public IEnumerable<UserViewModel> Users { get; set; } = new List<UserViewModel>();
|
||||
|
||||
public static TenantViewModel FromTenant(Tenant tenant)
|
||||
{
|
||||
return new TenantViewModel
|
||||
{
|
||||
Id = tenant.Id,
|
||||
Name = tenant.Name,
|
||||
Users = tenant.Users.Select(UserViewModel.FromUser)
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Application.ViewModels.Tenants;
|
||||
|
||||
public sealed record UpdateTenantViewModel(
|
||||
Guid Id,
|
||||
string Name);
|
@ -18,8 +18,8 @@ public sealed class UsersApiImplementation : UsersApi.UsersApiBase
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
public override async Task<GetByIdsResult> GetByIds(
|
||||
GetByIdsRequest request,
|
||||
public override async Task<GetUsersByIdsResult> GetByIds(
|
||||
GetUsersByIdsRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var idsAsGuids = new List<Guid>(request.Ids.Count);
|
||||
@ -45,7 +45,7 @@ public sealed class UsersApiImplementation : UsersApi.UsersApiBase
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
var result = new GetByIdsResult();
|
||||
var result = new GetUsersByIdsResult();
|
||||
|
||||
result.Users.AddRange(users);
|
||||
|
||||
|
@ -22,7 +22,7 @@ public sealed class ChangePasswordCommandHandlerTests
|
||||
_fixture
|
||||
.VerifyNoDomainNotification()
|
||||
.VerifyCommit()
|
||||
.VerifyRaisedEvent<PasswordChangedEvent>(x => x.UserId == user.Id);
|
||||
.VerifyRaisedEvent<PasswordChangedEvent>(x => x.AggregateId == user.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -40,7 +40,7 @@ public sealed class ChangePasswordCommandHandlerTests
|
||||
.VerifyAnyDomainNotification()
|
||||
.VerifyExistingNotification(
|
||||
ErrorCodes.ObjectNotFound,
|
||||
$"There is no User with Id {userId}");
|
||||
$"There is no user with Id {userId}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -57,7 +57,7 @@ public sealed class ChangePasswordCommandHandlerTests
|
||||
.VerifyNoRaisedEvent<UserUpdatedEvent>()
|
||||
.VerifyAnyDomainNotification()
|
||||
.VerifyExistingNotification(
|
||||
DomainErrorCodes.UserPasswordIncorrect,
|
||||
DomainErrorCodes.User.UserPasswordIncorrect,
|
||||
"The password is incorrect");
|
||||
}
|
||||
}
|
@ -28,12 +28,12 @@ public sealed class ChangePasswordCommandValidationTests :
|
||||
|
||||
var errors = new List<string>
|
||||
{
|
||||
DomainErrorCodes.UserEmptyPassword,
|
||||
DomainErrorCodes.UserSpecialCharPassword,
|
||||
DomainErrorCodes.UserNumberPassword,
|
||||
DomainErrorCodes.UserLowercaseLetterPassword,
|
||||
DomainErrorCodes.UserUppercaseLetterPassword,
|
||||
DomainErrorCodes.UserShortPassword
|
||||
DomainErrorCodes.User.UserEmptyPassword,
|
||||
DomainErrorCodes.User.UserSpecialCharPassword,
|
||||
DomainErrorCodes.User.UserNumberPassword,
|
||||
DomainErrorCodes.User.UserLowercaseLetterPassword,
|
||||
DomainErrorCodes.User.UserUppercaseLetterPassword,
|
||||
DomainErrorCodes.User.UserShortPassword
|
||||
};
|
||||
|
||||
ShouldHaveExpectedErrors(command, errors.ToArray());
|
||||
@ -44,7 +44,7 @@ public sealed class ChangePasswordCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand("z8tnayvd5FNLU9AQm");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserSpecialCharPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserSpecialCharPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -52,7 +52,7 @@ public sealed class ChangePasswordCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand("z]tnayvdFNLU:]AQm");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserNumberPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserNumberPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -60,7 +60,7 @@ public sealed class ChangePasswordCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand("Z8]TNAYVDFNLU:]AQM");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserLowercaseLetterPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserLowercaseLetterPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -68,7 +68,7 @@ public sealed class ChangePasswordCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand("z8]tnayvd5fnlu9:]aqm");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserUppercaseLetterPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserUppercaseLetterPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -76,7 +76,7 @@ public sealed class ChangePasswordCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand("zA6{");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserShortPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserShortPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -84,7 +84,7 @@ public sealed class ChangePasswordCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(string.Concat(Enumerable.Repeat("zA6{", 12), 12));
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserLongPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserLongPassword);
|
||||
}
|
||||
|
||||
private static ChangePasswordCommand CreateTestCommand(
|
||||
|
@ -28,7 +28,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
_fixture
|
||||
.VerifyNoDomainNotification()
|
||||
.VerifyCommit()
|
||||
.VerifyRaisedEvent<UserCreatedEvent>(x => x.UserId == command.UserId);
|
||||
.VerifyRaisedEvent<UserCreatedEvent>(x => x.AggregateId == command.UserId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -51,7 +51,7 @@ public sealed class CreateUserCommandHandlerTests
|
||||
.VerifyNoRaisedEvent<UserCreatedEvent>()
|
||||
.VerifyAnyDomainNotification()
|
||||
.VerifyExistingNotification(
|
||||
DomainErrorCodes.UserAlreadyExists,
|
||||
$"There is already a User with Id {command.UserId}");
|
||||
DomainErrorCodes.User.UserAlreadyExists,
|
||||
$"There is already a user with Id {command.UserId}");
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyId,
|
||||
DomainErrorCodes.User.UserEmptyId,
|
||||
"User id may not be empty");
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserInvalidEmail,
|
||||
DomainErrorCodes.User.UserInvalidEmail,
|
||||
"Email is not a valid email address");
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserInvalidEmail,
|
||||
DomainErrorCodes.User.UserInvalidEmail,
|
||||
"Email is not a valid email address");
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmailExceedsMaxLength,
|
||||
DomainErrorCodes.User.UserEmailExceedsMaxLength,
|
||||
"Email may not be longer than 320 characters");
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyFirstName,
|
||||
DomainErrorCodes.User.UserEmptyFirstName,
|
||||
"FirstName may not be empty");
|
||||
}
|
||||
|
||||
@ -84,7 +84,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserFirstNameExceedsMaxLength,
|
||||
DomainErrorCodes.User.UserFirstNameExceedsMaxLength,
|
||||
"FirstName may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyLastName,
|
||||
DomainErrorCodes.User.UserEmptyLastName,
|
||||
"LastName may not be empty");
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserLastNameExceedsMaxLength,
|
||||
DomainErrorCodes.User.UserLastNameExceedsMaxLength,
|
||||
"LastName may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
@ -117,12 +117,12 @@ public sealed class CreateUserCommandValidationTests :
|
||||
|
||||
var errors = new List<string>
|
||||
{
|
||||
DomainErrorCodes.UserEmptyPassword,
|
||||
DomainErrorCodes.UserSpecialCharPassword,
|
||||
DomainErrorCodes.UserNumberPassword,
|
||||
DomainErrorCodes.UserLowercaseLetterPassword,
|
||||
DomainErrorCodes.UserUppercaseLetterPassword,
|
||||
DomainErrorCodes.UserShortPassword
|
||||
DomainErrorCodes.User.UserEmptyPassword,
|
||||
DomainErrorCodes.User.UserSpecialCharPassword,
|
||||
DomainErrorCodes.User.UserNumberPassword,
|
||||
DomainErrorCodes.User.UserLowercaseLetterPassword,
|
||||
DomainErrorCodes.User.UserUppercaseLetterPassword,
|
||||
DomainErrorCodes.User.UserShortPassword
|
||||
};
|
||||
|
||||
ShouldHaveExpectedErrors(command, errors.ToArray());
|
||||
@ -133,7 +133,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: "z8tnayvd5FNLU9AQm");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserSpecialCharPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserSpecialCharPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -141,7 +141,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: "z]tnayvdFNLU:]AQm");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserNumberPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserNumberPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -149,7 +149,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: "Z8]TNAYVDFNLU:]AQM");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserLowercaseLetterPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserLowercaseLetterPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -157,7 +157,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: "z8]tnayvd5fnlu9:]aqm");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserUppercaseLetterPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserUppercaseLetterPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -165,7 +165,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: "zA6{");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserShortPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserShortPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -173,7 +173,7 @@ public sealed class CreateUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: string.Concat(Enumerable.Repeat("zA6{", 12), 12));
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserLongPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserLongPassword);
|
||||
}
|
||||
|
||||
private static CreateUserCommand CreateTestCommand(
|
||||
|
@ -22,7 +22,7 @@ public sealed class DeleteUserCommandHandlerTests
|
||||
_fixture
|
||||
.VerifyNoDomainNotification()
|
||||
.VerifyCommit()
|
||||
.VerifyRaisedEvent<UserDeletedEvent>(x => x.UserId == user.Id);
|
||||
.VerifyRaisedEvent<UserDeletedEvent>(x => x.AggregateId == user.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -40,6 +40,6 @@ public sealed class DeleteUserCommandHandlerTests
|
||||
.VerifyAnyDomainNotification()
|
||||
.VerifyExistingNotification(
|
||||
ErrorCodes.ObjectNotFound,
|
||||
$"There is no User with Id {command.UserId}");
|
||||
$"There is no user with Id {command.UserId}");
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ public sealed class DeleteUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyId,
|
||||
DomainErrorCodes.User.UserEmptyId,
|
||||
"User id may not be empty");
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ public sealed class LoginUserCommandHandlerTests
|
||||
.VerifyAnyDomainNotification()
|
||||
.VerifyExistingNotification(
|
||||
ErrorCodes.ObjectNotFound,
|
||||
$"There is no User with Email {command.Email}");
|
||||
$"There is no user with email {command.Email}");
|
||||
|
||||
token.Should().BeEmpty();
|
||||
}
|
||||
@ -74,7 +74,7 @@ public sealed class LoginUserCommandHandlerTests
|
||||
_fixture
|
||||
.VerifyAnyDomainNotification()
|
||||
.VerifyExistingNotification(
|
||||
DomainErrorCodes.UserPasswordIncorrect,
|
||||
DomainErrorCodes.User.UserPasswordIncorrect,
|
||||
"The password is incorrect");
|
||||
|
||||
token.Should().BeEmpty();
|
||||
|
@ -28,7 +28,7 @@ public sealed class LoginUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserInvalidEmail,
|
||||
DomainErrorCodes.User.UserInvalidEmail,
|
||||
"Email is not a valid email address");
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ public sealed class LoginUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserInvalidEmail,
|
||||
DomainErrorCodes.User.UserInvalidEmail,
|
||||
"Email is not a valid email address");
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ public sealed class LoginUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmailExceedsMaxLength,
|
||||
DomainErrorCodes.User.UserEmailExceedsMaxLength,
|
||||
"Email may not be longer than 320 characters");
|
||||
}
|
||||
|
||||
@ -61,12 +61,12 @@ public sealed class LoginUserCommandValidationTests :
|
||||
|
||||
var errors = new List<string>
|
||||
{
|
||||
DomainErrorCodes.UserEmptyPassword,
|
||||
DomainErrorCodes.UserSpecialCharPassword,
|
||||
DomainErrorCodes.UserNumberPassword,
|
||||
DomainErrorCodes.UserLowercaseLetterPassword,
|
||||
DomainErrorCodes.UserUppercaseLetterPassword,
|
||||
DomainErrorCodes.UserShortPassword
|
||||
DomainErrorCodes.User.UserEmptyPassword,
|
||||
DomainErrorCodes.User.UserSpecialCharPassword,
|
||||
DomainErrorCodes.User.UserNumberPassword,
|
||||
DomainErrorCodes.User.UserLowercaseLetterPassword,
|
||||
DomainErrorCodes.User.UserUppercaseLetterPassword,
|
||||
DomainErrorCodes.User.UserShortPassword
|
||||
};
|
||||
|
||||
ShouldHaveExpectedErrors(command, errors.ToArray());
|
||||
@ -77,7 +77,7 @@ public sealed class LoginUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: "z8tnayvd5FNLU9AQm");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserSpecialCharPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserSpecialCharPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -85,7 +85,7 @@ public sealed class LoginUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: "z]tnayvdFNLU:]AQm");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserNumberPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserNumberPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -93,7 +93,7 @@ public sealed class LoginUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: "Z8]TNAYVDFNLU:]AQM");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserLowercaseLetterPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserLowercaseLetterPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -101,7 +101,7 @@ public sealed class LoginUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: "z8]tnayvd5fnlu9:]aqm");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserUppercaseLetterPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserUppercaseLetterPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -109,7 +109,7 @@ public sealed class LoginUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: "zA6{");
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserShortPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserShortPassword);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -117,7 +117,7 @@ public sealed class LoginUserCommandValidationTests :
|
||||
{
|
||||
var command = CreateTestCommand(password: string.Concat(Enumerable.Repeat("zA6{", 12), 12));
|
||||
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.UserLongPassword);
|
||||
ShouldHaveSingleError(command, DomainErrorCodes.User.UserLongPassword);
|
||||
}
|
||||
|
||||
private static LoginUserCommand CreateTestCommand(
|
||||
|
@ -30,7 +30,7 @@ public sealed class UpdateUserCommandHandlerTests
|
||||
_fixture
|
||||
.VerifyNoDomainNotification()
|
||||
.VerifyCommit()
|
||||
.VerifyRaisedEvent<UserUpdatedEvent>(x => x.UserId == command.UserId);
|
||||
.VerifyRaisedEvent<UserUpdatedEvent>(x => x.AggregateId == command.UserId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -53,7 +53,7 @@ public sealed class UpdateUserCommandHandlerTests
|
||||
.VerifyAnyDomainNotification()
|
||||
.VerifyExistingNotification(
|
||||
ErrorCodes.ObjectNotFound,
|
||||
$"There is no User with Id {command.UserId}");
|
||||
$"There is no user with Id {command.UserId}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -86,7 +86,7 @@ public sealed class UpdateUserCommandHandlerTests
|
||||
.VerifyNoRaisedEvent<UserUpdatedEvent>()
|
||||
.VerifyAnyDomainNotification()
|
||||
.VerifyExistingNotification(
|
||||
DomainErrorCodes.UserAlreadyExists,
|
||||
$"There is already a User with Email {command.Email}");
|
||||
DomainErrorCodes.User.UserAlreadyExists,
|
||||
$"There is already a user with email {command.Email}");
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ public sealed class UpdateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyId,
|
||||
DomainErrorCodes.User.UserEmptyId,
|
||||
"User id may not be empty");
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ public sealed class UpdateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserInvalidEmail,
|
||||
DomainErrorCodes.User.UserInvalidEmail,
|
||||
"Email is not a valid email address");
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ public sealed class UpdateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserInvalidEmail,
|
||||
DomainErrorCodes.User.UserInvalidEmail,
|
||||
"Email is not a valid email address");
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ public sealed class UpdateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmailExceedsMaxLength,
|
||||
DomainErrorCodes.User.UserEmailExceedsMaxLength,
|
||||
"Email may not be longer than 320 characters");
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ public sealed class UpdateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyFirstName,
|
||||
DomainErrorCodes.User.UserEmptyFirstName,
|
||||
"FirstName may not be empty");
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ public sealed class UpdateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserFirstNameExceedsMaxLength,
|
||||
DomainErrorCodes.User.UserFirstNameExceedsMaxLength,
|
||||
"FirstName may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ public sealed class UpdateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserEmptyLastName,
|
||||
DomainErrorCodes.User.UserEmptyLastName,
|
||||
"LastName may not be empty");
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ public sealed class UpdateUserCommandValidationTests :
|
||||
|
||||
ShouldHaveSingleError(
|
||||
command,
|
||||
DomainErrorCodes.UserLastNameExceedsMaxLength,
|
||||
DomainErrorCodes.User.UserLastNameExceedsMaxLength,
|
||||
"LastName may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
|
@ -55,12 +55,12 @@ public sealed class ApiUser : IUser
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_name != null)
|
||||
if (_name is not null)
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
var identity = _httpContextAccessor.HttpContext?.User.Identity;
|
||||
if (identity == null)
|
||||
if (identity is null)
|
||||
{
|
||||
_name = string.Empty;
|
||||
return string.Empty;
|
||||
|
@ -62,7 +62,7 @@ public abstract class CommandHandlerBase
|
||||
return true;
|
||||
}
|
||||
|
||||
if (command.ValidationResult == null)
|
||||
if (command.ValidationResult is null)
|
||||
{
|
||||
throw new InvalidOperationException("Command is invalid and should therefore have a validation result");
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Tenants.CreateTenant;
|
||||
|
||||
public sealed class CreateTenantCommand : CommandBase
|
||||
{
|
||||
private static readonly CreateTenantCommandValidation s_validation = new();
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public CreateTenantCommand(Guid tenantId, string name) : base(tenantId)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
ValidationResult = s_validation.Validate(this);
|
||||
return ValidationResult.IsValid;
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Events.Tenant;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Tenants.CreateTenant;
|
||||
|
||||
public sealed class CreateTenantCommandHandler : CommandHandlerBase,
|
||||
IRequestHandler<CreateTenantCommand>
|
||||
{
|
||||
private readonly ITenantRepository _tenantRepository;
|
||||
|
||||
public CreateTenantCommandHandler(
|
||||
IMediatorHandler bus,
|
||||
IUnitOfWork unitOfWork,
|
||||
INotificationHandler<DomainNotification> notifications,
|
||||
ITenantRepository tenantRepository) : base(bus, unitOfWork, notifications)
|
||||
{
|
||||
_tenantRepository = tenantRepository;
|
||||
}
|
||||
|
||||
public async Task Handle(CreateTenantCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await TestValidityAsync(request))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (await _tenantRepository.ExistsAsync(request.AggregateId))
|
||||
{
|
||||
await NotifyAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is already a tenant with Id {request.AggregateId}",
|
||||
DomainErrorCodes.Tenant.TenantAlreadyExists));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var tenant = new Tenant(
|
||||
request.AggregateId,
|
||||
request.Name);
|
||||
|
||||
_tenantRepository.Add(tenant);
|
||||
|
||||
if (await CommitAsync())
|
||||
{
|
||||
await Bus.RaiseEventAsync(new TenantCreatedEvent(
|
||||
tenant.Id,
|
||||
tenant.Name));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
using CleanArchitecture.Domain.Constants;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using FluentValidation;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Tenants.CreateTenant;
|
||||
|
||||
public sealed class CreateTenantCommandValidation : AbstractValidator<CreateTenantCommand>
|
||||
{
|
||||
public CreateTenantCommandValidation()
|
||||
{
|
||||
AddRuleForId();
|
||||
AddRuleForName();
|
||||
}
|
||||
|
||||
private void AddRuleForId()
|
||||
{
|
||||
RuleFor(cmd => cmd.AggregateId)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.Tenant.TenantEmptyId)
|
||||
.WithMessage("Tenant id may not be empty");
|
||||
}
|
||||
|
||||
private void AddRuleForName()
|
||||
{
|
||||
RuleFor(cmd => cmd.Name)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.Tenant.TenantEmptyName)
|
||||
.WithMessage("Name may not be empty")
|
||||
.MaximumLength(MaxLengths.Tenant.Name)
|
||||
.WithErrorCode(DomainErrorCodes.Tenant.TenantNameExceedsMaxLength)
|
||||
.WithMessage($"Name may not be longer than {MaxLengths.Tenant.Name} characters");
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Tenants.DeleteTenant;
|
||||
|
||||
public sealed class DeleteTenantCommand : CommandBase
|
||||
{
|
||||
private static readonly DeleteTenantCommandValidation s_validation = new();
|
||||
|
||||
public DeleteTenantCommand(Guid tenantId) : base(tenantId)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
ValidationResult = s_validation.Validate(this);
|
||||
return ValidationResult.IsValid;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Events.Tenant;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Tenants.DeleteTenant;
|
||||
|
||||
public sealed class DeleteTenantCommandHandler : CommandHandlerBase,
|
||||
IRequestHandler<DeleteTenantCommand>
|
||||
{
|
||||
private readonly ITenantRepository _tenantRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public DeleteTenantCommandHandler(
|
||||
IMediatorHandler bus,
|
||||
IUnitOfWork unitOfWork,
|
||||
INotificationHandler<DomainNotification> notifications,
|
||||
ITenantRepository tenantRepository,
|
||||
IUserRepository userRepository) : base(bus, unitOfWork, notifications)
|
||||
{
|
||||
_tenantRepository = tenantRepository;
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
public async Task Handle(DeleteTenantCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await TestValidityAsync(request))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tenant = await _tenantRepository.GetByIdAsync(request.AggregateId);
|
||||
|
||||
if (tenant is null)
|
||||
{
|
||||
await NotifyAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is no tenant with Id {request.AggregateId}",
|
||||
ErrorCodes.ObjectNotFound));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var tenantUsers = _userRepository
|
||||
.GetAll()
|
||||
.Where(x => x.TenantId == request.AggregateId);
|
||||
|
||||
_userRepository.RemoveRange(tenantUsers);
|
||||
|
||||
_tenantRepository.Remove(tenant);
|
||||
|
||||
if (await CommitAsync())
|
||||
{
|
||||
await Bus.RaiseEventAsync(new TenantDeletedEvent(tenant.Id));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using FluentValidation;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Tenants.DeleteTenant;
|
||||
|
||||
public sealed class DeleteTenantCommandValidation : AbstractValidator<DeleteTenantCommand>
|
||||
{
|
||||
public DeleteTenantCommandValidation()
|
||||
{
|
||||
AddRuleForId();
|
||||
}
|
||||
|
||||
private void AddRuleForId()
|
||||
{
|
||||
RuleFor(cmd => cmd.AggregateId)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.Tenant.TenantEmptyId)
|
||||
.WithMessage("Tenant id may not be empty");
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Tenants.UpdateTenant;
|
||||
|
||||
public sealed class UpdateTenantCommand : CommandBase
|
||||
{
|
||||
private static readonly UpdateTenantCommandValidation s_validation = new();
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public UpdateTenantCommand(Guid tenantId, string name) : base(tenantId)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
ValidationResult = s_validation.Validate(this);
|
||||
return ValidationResult.IsValid;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using CleanArchitecture.Domain.Events.Tenant;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using CleanArchitecture.Domain.Notifications;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Tenants.UpdateTenant;
|
||||
|
||||
public sealed class UpdateTenantCommandHandler : CommandHandlerBase,
|
||||
IRequestHandler<UpdateTenantCommand>
|
||||
{
|
||||
private readonly ITenantRepository _tenantRepository;
|
||||
|
||||
public UpdateTenantCommandHandler(
|
||||
IMediatorHandler bus,
|
||||
IUnitOfWork unitOfWork,
|
||||
INotificationHandler<DomainNotification> notifications,
|
||||
ITenantRepository tenantRepository) : base(bus, unitOfWork, notifications)
|
||||
{
|
||||
_tenantRepository = tenantRepository;
|
||||
}
|
||||
|
||||
public async Task Handle(UpdateTenantCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await TestValidityAsync(request))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tenant = await _tenantRepository.GetByIdAsync(request.AggregateId);
|
||||
|
||||
if (tenant is null)
|
||||
{
|
||||
await NotifyAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is no tenant with Id {request.AggregateId}",
|
||||
ErrorCodes.ObjectNotFound));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
tenant.SetName(request.Name);
|
||||
|
||||
if (await CommitAsync())
|
||||
{
|
||||
await Bus.RaiseEventAsync(new TenantUpdatedEvent(
|
||||
tenant.Id,
|
||||
tenant.Name));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
using CleanArchitecture.Domain.Constants;
|
||||
using CleanArchitecture.Domain.Errors;
|
||||
using FluentValidation;
|
||||
|
||||
namespace CleanArchitecture.Domain.Commands.Tenants.UpdateTenant;
|
||||
|
||||
public sealed class UpdateTenantCommandValidation : AbstractValidator<UpdateTenantCommand>
|
||||
{
|
||||
public UpdateTenantCommandValidation()
|
||||
{
|
||||
AddRuleForId();
|
||||
AddRuleForName();
|
||||
}
|
||||
|
||||
private void AddRuleForId()
|
||||
{
|
||||
RuleFor(cmd => cmd.AggregateId)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.Tenant.TenantEmptyId)
|
||||
.WithMessage("Tenant id may not be empty");
|
||||
}
|
||||
|
||||
private void AddRuleForName()
|
||||
{
|
||||
RuleFor(cmd => cmd.Name)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.Tenant.TenantEmptyName)
|
||||
.WithMessage("Name may not be empty")
|
||||
.MaximumLength(MaxLengths.Tenant.Name)
|
||||
.WithErrorCode(DomainErrorCodes.Tenant.TenantNameExceedsMaxLength)
|
||||
.WithMessage($"Name may not be longer than {MaxLengths.Tenant.Name} characters");
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ namespace CleanArchitecture.Domain.Commands.Users.ChangePassword;
|
||||
|
||||
public sealed class ChangePasswordCommand : CommandBase
|
||||
{
|
||||
private readonly ChangePasswordCommandValidation _validation = new();
|
||||
private static readonly ChangePasswordCommandValidation s_validation = new();
|
||||
|
||||
public ChangePasswordCommand(string password, string newPassword) : base(Guid.NewGuid())
|
||||
{
|
||||
@ -17,7 +17,7 @@ public sealed class ChangePasswordCommand : CommandBase
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
ValidationResult = _validation.Validate(this);
|
||||
ValidationResult = s_validation.Validate(this);
|
||||
return ValidationResult.IsValid;
|
||||
}
|
||||
}
|
@ -36,12 +36,12 @@ public sealed class ChangePasswordCommandHandler : CommandHandlerBase,
|
||||
|
||||
var user = await _userRepository.GetByIdAsync(_user.GetUserId());
|
||||
|
||||
if (user == null)
|
||||
if (user is null)
|
||||
{
|
||||
await NotifyAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is no User with Id {_user.GetUserId()}",
|
||||
$"There is no user with Id {_user.GetUserId()}",
|
||||
ErrorCodes.ObjectNotFound));
|
||||
|
||||
return;
|
||||
@ -53,7 +53,7 @@ public sealed class ChangePasswordCommandHandler : CommandHandlerBase,
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
"The password is incorrect",
|
||||
DomainErrorCodes.UserPasswordIncorrect));
|
||||
DomainErrorCodes.User.UserPasswordIncorrect));
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ namespace CleanArchitecture.Domain.Commands.Users.CreateUser;
|
||||
|
||||
public sealed class CreateUserCommand : CommandBase
|
||||
{
|
||||
private readonly CreateUserCommandValidation _validation = new();
|
||||
private static readonly CreateUserCommandValidation s_validation = new();
|
||||
|
||||
public CreateUserCommand(
|
||||
Guid userId,
|
||||
@ -31,7 +31,7 @@ public sealed class CreateUserCommand : CommandBase
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
ValidationResult = _validation.Validate(this);
|
||||
ValidationResult = s_validation.Validate(this);
|
||||
return ValidationResult.IsValid;
|
||||
}
|
||||
}
|
@ -35,25 +35,25 @@ public sealed class CreateUserCommandHandler : CommandHandlerBase,
|
||||
|
||||
var existingUser = await _userRepository.GetByIdAsync(request.UserId);
|
||||
|
||||
if (existingUser != null)
|
||||
if (existingUser is not null)
|
||||
{
|
||||
await Bus.RaiseEventAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is already a User with Id {request.UserId}",
|
||||
DomainErrorCodes.UserAlreadyExists));
|
||||
$"There is already a user with Id {request.UserId}",
|
||||
DomainErrorCodes.User.UserAlreadyExists));
|
||||
return;
|
||||
}
|
||||
|
||||
existingUser = await _userRepository.GetByEmailAsync(request.Email);
|
||||
|
||||
if (existingUser != null)
|
||||
if (existingUser is not null)
|
||||
{
|
||||
await Bus.RaiseEventAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is already a User with Email {request.Email}",
|
||||
DomainErrorCodes.UserAlreadyExists));
|
||||
$"There is already a user with email {request.Email}",
|
||||
DomainErrorCodes.User.UserAlreadyExists));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ public sealed class CreateUserCommandValidation : AbstractValidator<CreateUserCo
|
||||
{
|
||||
RuleFor(cmd => cmd.UserId)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyId)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserEmptyId)
|
||||
.WithMessage("User id may not be empty");
|
||||
}
|
||||
|
||||
@ -28,10 +28,10 @@ public sealed class CreateUserCommandValidation : AbstractValidator<CreateUserCo
|
||||
{
|
||||
RuleFor(cmd => cmd.Email)
|
||||
.EmailAddress()
|
||||
.WithErrorCode(DomainErrorCodes.UserInvalidEmail)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserInvalidEmail)
|
||||
.WithMessage("Email is not a valid email address")
|
||||
.MaximumLength(MaxLengths.User.Email)
|
||||
.WithErrorCode(DomainErrorCodes.UserEmailExceedsMaxLength)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserEmailExceedsMaxLength)
|
||||
.WithMessage("Email may not be longer than 320 characters");
|
||||
}
|
||||
|
||||
@ -39,10 +39,10 @@ public sealed class CreateUserCommandValidation : AbstractValidator<CreateUserCo
|
||||
{
|
||||
RuleFor(cmd => cmd.FirstName)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyFirstName)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserEmptyFirstName)
|
||||
.WithMessage("FirstName may not be empty")
|
||||
.MaximumLength(MaxLengths.User.FirstName)
|
||||
.WithErrorCode(DomainErrorCodes.UserFirstNameExceedsMaxLength)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserFirstNameExceedsMaxLength)
|
||||
.WithMessage("FirstName may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
@ -50,10 +50,10 @@ public sealed class CreateUserCommandValidation : AbstractValidator<CreateUserCo
|
||||
{
|
||||
RuleFor(cmd => cmd.LastName)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyLastName)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserEmptyLastName)
|
||||
.WithMessage("LastName may not be empty")
|
||||
.MaximumLength(MaxLengths.User.LastName)
|
||||
.WithErrorCode(DomainErrorCodes.UserLastNameExceedsMaxLength)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserLastNameExceedsMaxLength)
|
||||
.WithMessage("LastName may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ namespace CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
|
||||
public sealed class DeleteUserCommand : CommandBase
|
||||
{
|
||||
private readonly DeleteUserCommandValidation _validation = new();
|
||||
private static readonly DeleteUserCommandValidation s_validation = new();
|
||||
|
||||
public DeleteUserCommand(Guid userId) : base(userId)
|
||||
{
|
||||
@ -15,7 +15,7 @@ public sealed class DeleteUserCommand : CommandBase
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
ValidationResult = _validation.Validate(this);
|
||||
ValidationResult = s_validation.Validate(this);
|
||||
return ValidationResult.IsValid;
|
||||
}
|
||||
}
|
@ -36,12 +36,12 @@ public sealed class DeleteUserCommandHandler : CommandHandlerBase,
|
||||
|
||||
var user = await _userRepository.GetByIdAsync(request.UserId);
|
||||
|
||||
if (user == null)
|
||||
if (user is null)
|
||||
{
|
||||
await NotifyAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is no User with Id {request.UserId}",
|
||||
$"There is no user with Id {request.UserId}",
|
||||
ErrorCodes.ObjectNotFound));
|
||||
|
||||
return;
|
||||
|
@ -14,7 +14,7 @@ public sealed class DeleteUserCommandValidation : AbstractValidator<DeleteUserCo
|
||||
{
|
||||
RuleFor(cmd => cmd.UserId)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyId)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserEmptyId)
|
||||
.WithMessage("User id may not be empty");
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ namespace CleanArchitecture.Domain.Commands.Users.LoginUser;
|
||||
public sealed class LoginUserCommand : CommandBase,
|
||||
IRequest<string>
|
||||
{
|
||||
private readonly LoginUserCommandValidation _validation = new();
|
||||
private static readonly LoginUserCommandValidation s_validation = new();
|
||||
|
||||
|
||||
public LoginUserCommand(
|
||||
@ -22,7 +22,7 @@ public sealed class LoginUserCommand : CommandBase,
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
ValidationResult = _validation.Validate(this);
|
||||
ValidationResult = s_validation.Validate(this);
|
||||
return ValidationResult.IsValid;
|
||||
}
|
||||
}
|
@ -45,12 +45,12 @@ public sealed class LoginUserCommandHandler : CommandHandlerBase,
|
||||
|
||||
var user = await _userRepository.GetByEmailAsync(request.Email);
|
||||
|
||||
if (user == null)
|
||||
if (user is null)
|
||||
{
|
||||
await NotifyAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is no User with Email {request.Email}",
|
||||
$"There is no user with email {request.Email}",
|
||||
ErrorCodes.ObjectNotFound));
|
||||
|
||||
return "";
|
||||
@ -64,7 +64,7 @@ public sealed class LoginUserCommandHandler : CommandHandlerBase,
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
"The password is incorrect",
|
||||
DomainErrorCodes.UserPasswordIncorrect));
|
||||
DomainErrorCodes.User.UserPasswordIncorrect));
|
||||
|
||||
return "";
|
||||
}
|
||||
|
@ -17,10 +17,10 @@ public sealed class LoginUserCommandValidation : AbstractValidator<LoginUserComm
|
||||
{
|
||||
RuleFor(cmd => cmd.Email)
|
||||
.EmailAddress()
|
||||
.WithErrorCode(DomainErrorCodes.UserInvalidEmail)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserInvalidEmail)
|
||||
.WithMessage("Email is not a valid email address")
|
||||
.MaximumLength(MaxLengths.User.Email)
|
||||
.WithErrorCode(DomainErrorCodes.UserEmailExceedsMaxLength)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserEmailExceedsMaxLength)
|
||||
.WithMessage("Email may not be longer than 320 characters");
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ namespace CleanArchitecture.Domain.Commands.Users.UpdateUser;
|
||||
|
||||
public sealed class UpdateUserCommand : CommandBase
|
||||
{
|
||||
private readonly UpdateUserCommandValidation _validation = new();
|
||||
private static readonly UpdateUserCommandValidation s_validation = new();
|
||||
|
||||
public UpdateUserCommand(
|
||||
Guid userId,
|
||||
@ -29,7 +29,7 @@ public sealed class UpdateUserCommand : CommandBase
|
||||
|
||||
public override bool IsValid()
|
||||
{
|
||||
ValidationResult = _validation.Validate(this);
|
||||
ValidationResult = s_validation.Validate(this);
|
||||
return ValidationResult.IsValid;
|
||||
}
|
||||
}
|
@ -36,12 +36,12 @@ public sealed class UpdateUserCommandHandler : CommandHandlerBase,
|
||||
|
||||
var user = await _userRepository.GetByIdAsync(request.UserId);
|
||||
|
||||
if (user == null)
|
||||
if (user is null)
|
||||
{
|
||||
await Bus.RaiseEventAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is no User with Id {request.UserId}",
|
||||
$"There is no user with Id {request.UserId}",
|
||||
ErrorCodes.ObjectNotFound));
|
||||
return;
|
||||
}
|
||||
@ -61,13 +61,13 @@ public sealed class UpdateUserCommandHandler : CommandHandlerBase,
|
||||
{
|
||||
var existingUser = await _userRepository.GetByEmailAsync(request.Email);
|
||||
|
||||
if (existingUser != null)
|
||||
if (existingUser is not null)
|
||||
{
|
||||
await Bus.RaiseEventAsync(
|
||||
new DomainNotification(
|
||||
request.MessageType,
|
||||
$"There is already a User with Email {request.Email}",
|
||||
DomainErrorCodes.UserAlreadyExists));
|
||||
$"There is already a user with email {request.Email}",
|
||||
DomainErrorCodes.User.UserAlreadyExists));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCo
|
||||
{
|
||||
RuleFor(cmd => cmd.UserId)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyId)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserEmptyId)
|
||||
.WithMessage("User id may not be empty");
|
||||
}
|
||||
|
||||
@ -27,10 +27,10 @@ public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCo
|
||||
{
|
||||
RuleFor(cmd => cmd.Email)
|
||||
.EmailAddress()
|
||||
.WithErrorCode(DomainErrorCodes.UserInvalidEmail)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserInvalidEmail)
|
||||
.WithMessage("Email is not a valid email address")
|
||||
.MaximumLength(MaxLengths.User.Email)
|
||||
.WithErrorCode(DomainErrorCodes.UserEmailExceedsMaxLength)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserEmailExceedsMaxLength)
|
||||
.WithMessage("Email may not be longer than 320 characters");
|
||||
}
|
||||
|
||||
@ -38,10 +38,10 @@ public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCo
|
||||
{
|
||||
RuleFor(cmd => cmd.FirstName)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyFirstName)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserEmptyFirstName)
|
||||
.WithMessage("FirstName may not be empty")
|
||||
.MaximumLength(MaxLengths.User.FirstName)
|
||||
.WithErrorCode(DomainErrorCodes.UserFirstNameExceedsMaxLength)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserFirstNameExceedsMaxLength)
|
||||
.WithMessage("FirstName may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
@ -49,10 +49,10 @@ public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCo
|
||||
{
|
||||
RuleFor(cmd => cmd.LastName)
|
||||
.NotEmpty()
|
||||
.WithErrorCode(DomainErrorCodes.UserEmptyLastName)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserEmptyLastName)
|
||||
.WithMessage("LastName may not be empty")
|
||||
.MaximumLength(MaxLengths.User.LastName)
|
||||
.WithErrorCode(DomainErrorCodes.UserLastNameExceedsMaxLength)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserLastNameExceedsMaxLength)
|
||||
.WithMessage("LastName may not be longer than 100 characters");
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCo
|
||||
{
|
||||
RuleFor(cmd => cmd.Role)
|
||||
.IsInEnum()
|
||||
.WithErrorCode(DomainErrorCodes.UserInvalidRole)
|
||||
.WithErrorCode(DomainErrorCodes.User.UserInvalidRole)
|
||||
.WithMessage("Role is not a valid role");
|
||||
}
|
||||
}
|
@ -15,4 +15,9 @@ public class Tenant : Entity
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void SetName(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
@ -2,26 +2,40 @@ namespace CleanArchitecture.Domain.Errors;
|
||||
|
||||
public static class DomainErrorCodes
|
||||
{
|
||||
// User Validation
|
||||
public const string UserEmptyId = "USER_EMPTY_ID";
|
||||
public const string UserEmptyFirstName = "USER_EMPTY_FIRST_NAME";
|
||||
public const string UserEmptyLastName = "USER_EMPTY_LAST_NAME";
|
||||
public const string UserEmailExceedsMaxLength = "USER_EMAIL_EXCEEDS_MAX_LENGTH";
|
||||
public const string UserFirstNameExceedsMaxLength = "USER_FIRST_NAME_EXCEEDS_MAX_LENGTH";
|
||||
public const string UserLastNameExceedsMaxLength = "USER_LAST_NAME_EXCEEDS_MAX_LENGTH";
|
||||
public const string UserInvalidEmail = "USER_INVALID_EMAIL";
|
||||
public const string UserInvalidRole = "USER_INVALID_ROLE";
|
||||
public static class User
|
||||
{
|
||||
// User Validation
|
||||
public const string UserEmptyId = "USER_EMPTY_ID";
|
||||
public const string UserEmptyFirstName = "USER_EMPTY_FIRST_NAME";
|
||||
public const string UserEmptyLastName = "USER_EMPTY_LAST_NAME";
|
||||
public const string UserEmailExceedsMaxLength = "USER_EMAIL_EXCEEDS_MAX_LENGTH";
|
||||
public const string UserFirstNameExceedsMaxLength = "USER_FIRST_NAME_EXCEEDS_MAX_LENGTH";
|
||||
public const string UserLastNameExceedsMaxLength = "USER_LAST_NAME_EXCEEDS_MAX_LENGTH";
|
||||
public const string UserInvalidEmail = "USER_INVALID_EMAIL";
|
||||
public const string UserInvalidRole = "USER_INVALID_ROLE";
|
||||
|
||||
// User Password Validation
|
||||
public const string UserEmptyPassword = "USER_PASSWORD_MAY_NOT_BE_EMPTY";
|
||||
public const string UserShortPassword = "USER_PASSWORD_MAY_NOT_BE_SHORTER_THAN_6_CHARACTERS";
|
||||
public const string UserLongPassword = "USER_PASSWORD_MAY_NOT_BE_LONGER_THAN_50_CHARACTERS";
|
||||
public const string UserUppercaseLetterPassword = "USER_PASSWORD_MUST_CONTAIN_A_UPPERCASE_LETTER";
|
||||
public const string UserLowercaseLetterPassword = "USER_PASSWORD_MUST_CONTAIN_A_LOWERCASE_LETTER";
|
||||
public const string UserNumberPassword = "USER_PASSWORD_MUST_CONTAIN_A_NUMBER";
|
||||
public const string UserSpecialCharPassword = "USER_PASSWORD_MUST_CONTAIN_A_SPECIAL_CHARACTER";
|
||||
// User Password Validation
|
||||
public const string UserEmptyPassword = "USER_PASSWORD_MAY_NOT_BE_EMPTY";
|
||||
public const string UserShortPassword = "USER_PASSWORD_MAY_NOT_BE_SHORTER_THAN_6_CHARACTERS";
|
||||
public const string UserLongPassword = "USER_PASSWORD_MAY_NOT_BE_LONGER_THAN_50_CHARACTERS";
|
||||
public const string UserUppercaseLetterPassword = "USER_PASSWORD_MUST_CONTAIN_A_UPPERCASE_LETTER";
|
||||
public const string UserLowercaseLetterPassword = "USER_PASSWORD_MUST_CONTAIN_A_LOWERCASE_LETTER";
|
||||
public const string UserNumberPassword = "USER_PASSWORD_MUST_CONTAIN_A_NUMBER";
|
||||
public const string UserSpecialCharPassword = "USER_PASSWORD_MUST_CONTAIN_A_SPECIAL_CHARACTER";
|
||||
|
||||
// User
|
||||
public const string UserAlreadyExists = "USER_ALREADY_EXISTS";
|
||||
public const string UserPasswordIncorrect = "USER_PASSWORD_INCORRECT";
|
||||
// General
|
||||
public const string UserAlreadyExists = "USER_ALREADY_EXISTS";
|
||||
public const string UserPasswordIncorrect = "USER_PASSWORD_INCORRECT";
|
||||
}
|
||||
|
||||
public static class Tenant
|
||||
{
|
||||
// Tenant Validation
|
||||
public const string TenantEmptyId = "TENANT_EMPTY_ID";
|
||||
public const string TenantEmptyName = "TENANT_EMPTY_NAME";
|
||||
public const string TenantNameExceedsMaxLength = "TENANT_NAME_EXCEEDS_MAX_LENGTH";
|
||||
|
||||
// General
|
||||
public const string TenantAlreadyExists = "TENANT_ALREADY_EXISTS";
|
||||
}
|
||||
}
|
27
CleanArchitecture.Domain/EventHandler/TenantEventHandler.cs
Normal file
27
CleanArchitecture.Domain/EventHandler/TenantEventHandler.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Domain.Events.Tenant;
|
||||
using MediatR;
|
||||
|
||||
namespace CleanArchitecture.Domain.EventHandler;
|
||||
|
||||
public sealed class TenantEventHandler :
|
||||
INotificationHandler<TenantCreatedEvent>,
|
||||
INotificationHandler<TenantDeletedEvent>,
|
||||
INotificationHandler<TenantUpdatedEvent>
|
||||
{
|
||||
public Task Handle(TenantCreatedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Handle(TenantDeletedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Handle(TenantUpdatedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
14
CleanArchitecture.Domain/Events/Tenant/TenantCreatedEvent.cs
Normal file
14
CleanArchitecture.Domain/Events/Tenant/TenantCreatedEvent.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using CleanArchitecture.Domain.DomainEvents;
|
||||
|
||||
namespace CleanArchitecture.Domain.Events.Tenant;
|
||||
|
||||
public sealed class TenantCreatedEvent : DomainEvent
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public TenantCreatedEvent(Guid tenantId, string name) : base(tenantId)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
11
CleanArchitecture.Domain/Events/Tenant/TenantDeletedEvent.cs
Normal file
11
CleanArchitecture.Domain/Events/Tenant/TenantDeletedEvent.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using CleanArchitecture.Domain.DomainEvents;
|
||||
|
||||
namespace CleanArchitecture.Domain.Events.Tenant;
|
||||
|
||||
public sealed class TenantDeletedEvent : DomainEvent
|
||||
{
|
||||
public TenantDeletedEvent(Guid tenantId) : base(tenantId)
|
||||
{
|
||||
}
|
||||
}
|
14
CleanArchitecture.Domain/Events/Tenant/TenantUpdatedEvent.cs
Normal file
14
CleanArchitecture.Domain/Events/Tenant/TenantUpdatedEvent.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using CleanArchitecture.Domain.DomainEvents;
|
||||
|
||||
namespace CleanArchitecture.Domain.Events.Tenant;
|
||||
|
||||
public sealed class TenantUpdatedEvent : DomainEvent
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public TenantUpdatedEvent(Guid tenantId, string name) : base(tenantId)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
@ -7,8 +7,5 @@ public sealed class PasswordChangedEvent : DomainEvent
|
||||
{
|
||||
public PasswordChangedEvent(Guid userId) : base(userId)
|
||||
{
|
||||
UserId = userId;
|
||||
}
|
||||
|
||||
public Guid UserId { get; }
|
||||
}
|
@ -7,8 +7,5 @@ public sealed class UserCreatedEvent : DomainEvent
|
||||
{
|
||||
public UserCreatedEvent(Guid userId) : base(userId)
|
||||
{
|
||||
UserId = userId;
|
||||
}
|
||||
|
||||
public Guid UserId { get; }
|
||||
}
|
@ -7,8 +7,5 @@ public sealed class UserDeletedEvent : DomainEvent
|
||||
{
|
||||
public UserDeletedEvent(Guid userId) : base(userId)
|
||||
{
|
||||
UserId = userId;
|
||||
}
|
||||
|
||||
public Guid UserId { get; }
|
||||
}
|
@ -7,8 +7,5 @@ public sealed class UserUpdatedEvent : DomainEvent
|
||||
{
|
||||
public UserUpdatedEvent(Guid userId) : base(userId)
|
||||
{
|
||||
UserId = userId;
|
||||
}
|
||||
|
||||
public Guid UserId { get; }
|
||||
}
|
@ -1,9 +1,13 @@
|
||||
using CleanArchitecture.Domain.Commands.Tenants.CreateTenant;
|
||||
using CleanArchitecture.Domain.Commands.Tenants.DeleteTenant;
|
||||
using CleanArchitecture.Domain.Commands.Tenants.UpdateTenant;
|
||||
using CleanArchitecture.Domain.Commands.Users.ChangePassword;
|
||||
using CleanArchitecture.Domain.Commands.Users.CreateUser;
|
||||
using CleanArchitecture.Domain.Commands.Users.DeleteUser;
|
||||
using CleanArchitecture.Domain.Commands.Users.LoginUser;
|
||||
using CleanArchitecture.Domain.Commands.Users.UpdateUser;
|
||||
using CleanArchitecture.Domain.EventHandler;
|
||||
using CleanArchitecture.Domain.Events.Tenant;
|
||||
using CleanArchitecture.Domain.Events.User;
|
||||
using CleanArchitecture.Domain.Interfaces;
|
||||
using MediatR;
|
||||
@ -22,6 +26,10 @@ public static class ServiceCollectionExtension
|
||||
services.AddScoped<IRequestHandler<ChangePasswordCommand>, ChangePasswordCommandHandler>();
|
||||
services.AddScoped<IRequestHandler<LoginUserCommand, string>, LoginUserCommandHandler>();
|
||||
|
||||
// Tenant
|
||||
services.AddScoped<IRequestHandler<CreateTenantCommand>, CreateTenantCommandHandler>();
|
||||
services.AddScoped<IRequestHandler<UpdateTenantCommand>, UpdateTenantCommandHandler>();
|
||||
services.AddScoped<IRequestHandler<DeleteTenantCommand>, DeleteTenantCommandHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
@ -33,6 +41,11 @@ public static class ServiceCollectionExtension
|
||||
services.AddScoped<INotificationHandler<UserUpdatedEvent>, UserEventHandler>();
|
||||
services.AddScoped<INotificationHandler<UserDeletedEvent>, UserEventHandler>();
|
||||
services.AddScoped<INotificationHandler<PasswordChangedEvent>, UserEventHandler>();
|
||||
|
||||
// Tenant
|
||||
services.AddScoped<INotificationHandler<TenantCreatedEvent>, TenantEventHandler>();
|
||||
services.AddScoped<INotificationHandler<TenantUpdatedEvent>, TenantEventHandler>();
|
||||
services.AddScoped<INotificationHandler<TenantDeletedEvent>, TenantEventHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
@ -23,13 +23,13 @@ public static partial class CustomValidator
|
||||
int maxLength = 50)
|
||||
{
|
||||
var options = ruleBuilder
|
||||
.NotEmpty().WithErrorCode(DomainErrorCodes.UserEmptyPassword)
|
||||
.MinimumLength(minLength).WithErrorCode(DomainErrorCodes.UserShortPassword)
|
||||
.MaximumLength(maxLength).WithErrorCode(DomainErrorCodes.UserLongPassword)
|
||||
.Matches("[A-Z]").WithErrorCode(DomainErrorCodes.UserUppercaseLetterPassword)
|
||||
.Matches("[a-z]").WithErrorCode(DomainErrorCodes.UserLowercaseLetterPassword)
|
||||
.Matches("[0-9]").WithErrorCode(DomainErrorCodes.UserNumberPassword)
|
||||
.Matches("[^a-zA-Z0-9]").WithErrorCode(DomainErrorCodes.UserSpecialCharPassword);
|
||||
.NotEmpty().WithErrorCode(DomainErrorCodes.User.UserEmptyPassword)
|
||||
.MinimumLength(minLength).WithErrorCode(DomainErrorCodes.User.UserShortPassword)
|
||||
.MaximumLength(maxLength).WithErrorCode(DomainErrorCodes.User.UserLongPassword)
|
||||
.Matches("[A-Z]").WithErrorCode(DomainErrorCodes.User.UserUppercaseLetterPassword)
|
||||
.Matches("[a-z]").WithErrorCode(DomainErrorCodes.User.UserLowercaseLetterPassword)
|
||||
.Matches("[0-9]").WithErrorCode(DomainErrorCodes.User.UserNumberPassword)
|
||||
.Matches("[^a-zA-Z0-9]").WithErrorCode(DomainErrorCodes.User.UserSpecialCharPassword);
|
||||
return options;
|
||||
}
|
||||
|
||||
|
@ -22,4 +22,5 @@ public interface IRepository<TEntity> : IDisposable where TEntity : Entity
|
||||
|
||||
Task<bool> ExistsAsync(Guid id);
|
||||
public void Remove(TEntity entity, bool hardDelete = false);
|
||||
void RemoveRange(IEnumerable<TEntity> entities, bool hardDelete = false);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
|
||||
namespace CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
|
||||
public interface ITenantRepository : IRepository<Tenant>
|
||||
{
|
||||
}
|
@ -13,7 +13,7 @@ public sealed class EventStoreContext : IEventStoreContext
|
||||
{
|
||||
_user = user;
|
||||
|
||||
if (httpContextAccessor?.HttpContext == null ||
|
||||
if (httpContextAccessor?.HttpContext is null ||
|
||||
!httpContextAccessor.HttpContext.Request.Headers.TryGetValue("X-CLEAN-ARCHITECTURE-CORRELATION-ID", out var id))
|
||||
{
|
||||
_correlationId = $"internal - {Guid.NewGuid()}";
|
||||
|
@ -46,6 +46,7 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
// Repositories
|
||||
services.AddScoped<IUserRepository, UserRepository>();
|
||||
services.AddScoped<ITenantRepository, TenantRepository>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
@ -55,9 +55,9 @@ public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : Enti
|
||||
DbSet.Update(entity);
|
||||
}
|
||||
|
||||
public Task<bool> ExistsAsync(Guid id)
|
||||
public async Task<bool> ExistsAsync(Guid id)
|
||||
{
|
||||
return DbSet.AnyAsync(entity => entity.Id == id);
|
||||
return await DbSet.AnyAsync(entity => entity.Id == id);
|
||||
}
|
||||
|
||||
public void Remove(TEntity entity, bool hardDelete = false)
|
||||
@ -72,6 +72,20 @@ public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : Enti
|
||||
DbSet.Update(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRange(IEnumerable<TEntity> entities, bool hardDelete = false)
|
||||
{
|
||||
if (hardDelete)
|
||||
{
|
||||
DbSet.RemoveRange(entities);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
entity.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
public int SaveChanges()
|
||||
{
|
||||
|
@ -0,0 +1,12 @@
|
||||
using CleanArchitecture.Domain.Entities;
|
||||
using CleanArchitecture.Domain.Interfaces.Repositories;
|
||||
using CleanArchitecture.Infrastructure.Database;
|
||||
|
||||
namespace CleanArchitecture.Infrastructure.Repositories;
|
||||
|
||||
public sealed class TenantRepository : BaseRepository<Tenant>, ITenantRepository
|
||||
{
|
||||
public TenantRepository(ApplicationDbContext context) : base(context)
|
||||
{
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ public static class FunctionalTestsServiceCollectionExtensions
|
||||
DbConnection connection) where TContext : DbContext
|
||||
{
|
||||
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<TContext>));
|
||||
if (descriptor != null)
|
||||
if (descriptor is not null)
|
||||
services.Remove(descriptor);
|
||||
|
||||
services.AddScoped(p =>
|
||||
|
@ -21,7 +21,7 @@ public sealed class GetUsersByIdsTests : IClassFixture<GetUsersByIdsTestFixture>
|
||||
{
|
||||
var client = new UsersApi.UsersApiClient(_fixture.GrpcChannel);
|
||||
|
||||
var request = new GetByIdsRequest();
|
||||
var request = new GetUsersByIdsRequest();
|
||||
request.Ids.Add(_fixture.CreatedUserId.ToString());
|
||||
|
||||
var response = await client.GetByIdsAsync(request);
|
||||
|
@ -5,14 +5,11 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Users\Models.proto" />
|
||||
<None Remove="Users\UsersApi.proto" />
|
||||
</ItemGroup>
|
||||
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
17
CleanArchitecture.Proto/Tenants/Models.proto
Normal file
17
CleanArchitecture.Proto/Tenants/Models.proto
Normal file
@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "CleanArchitecture.Proto.Tenants";
|
||||
|
||||
message Tenant {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
bool isDeleted = 3;
|
||||
}
|
||||
|
||||
message GetTenantsByIdsResult {
|
||||
repeated Tenant tenants = 1;
|
||||
}
|
||||
|
||||
message GetTenantsByIdsRequest {
|
||||
repeated string ids = 1;
|
||||
}
|
9
CleanArchitecture.Proto/Tenants/TenantsApi.proto
Normal file
9
CleanArchitecture.Proto/Tenants/TenantsApi.proto
Normal file
@ -0,0 +1,9 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option csharp_namespace = "CleanArchitecture.Proto.Tenants";
|
||||
|
||||
import "Tenants/Models.proto";
|
||||
|
||||
service TenantsApi {
|
||||
rpc GetByIds(GetTenantsByIdsRequest) returns (GetTenantsByIdsResult);
|
||||
}
|
@ -10,10 +10,10 @@ message GrpcUser {
|
||||
bool isDeleted = 6;
|
||||
}
|
||||
|
||||
message GetByIdsResult {
|
||||
message GetUsersByIdsResult {
|
||||
repeated GrpcUser users = 1;
|
||||
}
|
||||
|
||||
message GetByIdsRequest {
|
||||
message GetUsersByIdsRequest {
|
||||
repeated string ids = 1;
|
||||
}
|
@ -5,5 +5,5 @@ option csharp_namespace = "CleanArchitecture.Proto.Users";
|
||||
import "Users/Models.proto";
|
||||
|
||||
service UsersApi {
|
||||
rpc GetByIds(GetByIdsRequest) returns (GetByIdsResult);
|
||||
rpc GetByIds(GetUsersByIdsRequest) returns (GetUsersByIdsResult);
|
||||
}
|
||||
|
@ -5,4 +5,5 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
7
CleanArchitecture.Shared/Tenants/TenantViewModel.cs
Normal file
7
CleanArchitecture.Shared/Tenants/TenantViewModel.cs
Normal file
@ -0,0 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace CleanArchitecture.Shared.Tenants;
|
||||
|
||||
public sealed record TenantViewModel(
|
||||
Guid Id,
|
||||
string Name);
|
@ -62,9 +62,9 @@ public sealed class GetUsersByIdsTests : IClassFixture<UserTestsFixture>
|
||||
}
|
||||
}
|
||||
|
||||
private static GetByIdsRequest SetupRequest(IEnumerable<Guid> ids)
|
||||
private static GetUsersByIdsRequest SetupRequest(IEnumerable<Guid> ids)
|
||||
{
|
||||
var request = new GetByIdsRequest();
|
||||
var request = new GetUsersByIdsRequest();
|
||||
|
||||
request.Ids.AddRange(ids.Select(id => id.ToString()));
|
||||
request.Ids.Add("Not a guid");
|
||||
|
@ -5,11 +5,17 @@ namespace CleanArchitecture.gRPC;
|
||||
public sealed class CleanArchitecture : ICleanArchitecture
|
||||
{
|
||||
private readonly IUsersContext _users;
|
||||
private readonly ITenantsContext _tenants;
|
||||
|
||||
public IUsersContext Users => _users;
|
||||
public ITenantsContext Tenants => _tenants;
|
||||
|
||||
public CleanArchitecture(IUsersContext users)
|
||||
public CleanArchitecture(
|
||||
IUsersContext users,
|
||||
ITenantsContext tenants)
|
||||
{
|
||||
_users = users;
|
||||
_tenants = tenants;
|
||||
|
||||
}
|
||||
}
|
||||
|
32
CleanArchitecture.gRPC/Contexts/TenantsContext.cs
Normal file
32
CleanArchitecture.gRPC/Contexts/TenantsContext.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.gRPC.Interfaces;
|
||||
using CleanArchitecture.Proto.Tenants;
|
||||
using CleanArchitecture.Shared.Tenants;
|
||||
|
||||
namespace CleanArchitecture.gRPC.Contexts;
|
||||
|
||||
public sealed class TenantsContext : ITenantsContext
|
||||
{
|
||||
private readonly TenantsApi.TenantsApiClient _client;
|
||||
|
||||
public TenantsContext(TenantsApi.TenantsApiClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TenantViewModel>> GetTenantsByIds(IEnumerable<Guid> ids)
|
||||
{
|
||||
var request = new GetTenantsByIdsRequest();
|
||||
|
||||
request.Ids.AddRange(ids.Select(id => id.ToString()));
|
||||
|
||||
var result = await _client.GetByIdsAsync(request);
|
||||
|
||||
return result.Tenants.Select(tenant => new TenantViewModel(
|
||||
Guid.Parse(tenant.Id),
|
||||
tenant.Name));
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ public sealed class UsersContext : IUsersContext
|
||||
|
||||
public async Task<IEnumerable<UserViewModel>> GetUsersByIds(IEnumerable<Guid> ids)
|
||||
{
|
||||
var request = new GetByIdsRequest();
|
||||
var request = new GetUsersByIdsRequest();
|
||||
|
||||
request.Ids.AddRange(ids.Select(id => id.ToString()));
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using CleanArchitecture.gRPC.Contexts;
|
||||
using CleanArchitecture.gRPC.Interfaces;
|
||||
using CleanArchitecture.gRPC.Models;
|
||||
using CleanArchitecture.Proto.Tenants;
|
||||
using CleanArchitecture.Proto.Users;
|
||||
using Grpc.Net.Client;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@ -35,20 +36,24 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
public static IServiceCollection AddCleanArchitectureGrpcClient(
|
||||
this IServiceCollection services,
|
||||
string tetraQueryApiUrl)
|
||||
string gRPCUrl)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tetraQueryApiUrl))
|
||||
if (string.IsNullOrWhiteSpace(gRPCUrl))
|
||||
{
|
||||
return services;
|
||||
}
|
||||
|
||||
var channel = GrpcChannel.ForAddress(tetraQueryApiUrl);
|
||||
var channel = GrpcChannel.ForAddress(gRPCUrl);
|
||||
|
||||
var usersClient = new UsersApi.UsersApiClient(channel);
|
||||
services.AddSingleton(usersClient);
|
||||
|
||||
services.AddSingleton<IUsersContext, UsersContext>();
|
||||
var tenantsClient = new TenantsApi.TenantsApiClient(channel);
|
||||
services.AddSingleton(tenantsClient);
|
||||
|
||||
services.AddSingleton<IUsersContext, UsersContext>();
|
||||
services.AddSingleton<ITenantsContext, TenantsContext>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
@ -5,4 +5,5 @@ namespace CleanArchitecture.gRPC;
|
||||
public interface ICleanArchitecture
|
||||
{
|
||||
IUsersContext Users { get; }
|
||||
ITenantsContext Tenants { get; }
|
||||
}
|
||||
|
11
CleanArchitecture.gRPC/Interfaces/ITenantsContext.cs
Normal file
11
CleanArchitecture.gRPC/Interfaces/ITenantsContext.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CleanArchitecture.Shared.Tenants;
|
||||
|
||||
namespace CleanArchitecture.gRPC.Interfaces;
|
||||
|
||||
public interface ITenantsContext
|
||||
{
|
||||
Task<IEnumerable<TenantViewModel>> GetTenantsByIds(IEnumerable<Guid> ids);
|
||||
}
|
Loading…
Reference in New Issue
Block a user