116 lines
4.5 KiB
C#
116 lines
4.5 KiB
C#
using ColaFlow.Modules.Identity.Application.Services;
|
|
using ColaFlow.Modules.Identity.Domain.Aggregates.Invitations;
|
|
using ColaFlow.Modules.Identity.Domain.Aggregates.Tenants;
|
|
using ColaFlow.Modules.Identity.Domain.Aggregates.Users;
|
|
using ColaFlow.Modules.Identity.Domain.Repositories;
|
|
using ColaFlow.Modules.Identity.Domain.Services;
|
|
using MediatR;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace ColaFlow.Modules.Identity.Application.Commands.InviteUser;
|
|
|
|
public class InviteUserCommandHandler(
|
|
IInvitationRepository invitationRepository,
|
|
IUserRepository userRepository,
|
|
IUserTenantRoleRepository userTenantRoleRepository,
|
|
ITenantRepository tenantRepository,
|
|
ISecurityTokenService tokenService,
|
|
IEmailService emailService,
|
|
IEmailTemplateService templateService,
|
|
ILogger<InviteUserCommandHandler> logger)
|
|
: IRequestHandler<InviteUserCommand, Guid>
|
|
{
|
|
public async Task<Guid> Handle(InviteUserCommand request, CancellationToken cancellationToken)
|
|
{
|
|
var tenantId = TenantId.Create(request.TenantId);
|
|
var invitedBy = UserId.Create(request.InvitedBy);
|
|
|
|
// Validate role
|
|
if (!Enum.TryParse<TenantRole>(request.Role, out var role))
|
|
throw new ArgumentException($"Invalid role: {request.Role}");
|
|
|
|
// Check if tenant exists
|
|
var tenant = await tenantRepository.GetByIdAsync(tenantId, cancellationToken);
|
|
if (tenant == null)
|
|
throw new InvalidOperationException($"Tenant {request.TenantId} not found");
|
|
|
|
// Check if inviter exists
|
|
var inviter = await userRepository.GetByIdAsync(invitedBy, cancellationToken);
|
|
if (inviter == null)
|
|
throw new InvalidOperationException($"Inviter user {request.InvitedBy} not found");
|
|
|
|
var email = Email.Create(request.Email);
|
|
|
|
// Check if user already exists in this tenant
|
|
var existingUser = await userRepository.GetByEmailAsync(tenantId, email, cancellationToken);
|
|
if (existingUser != null)
|
|
{
|
|
// Check if user already has a role in this tenant
|
|
var existingRole = await userTenantRoleRepository.GetByUserAndTenantAsync(
|
|
UserId.Create(existingUser.Id),
|
|
tenantId,
|
|
cancellationToken);
|
|
|
|
if (existingRole != null)
|
|
throw new InvalidOperationException($"User with email {request.Email} is already a member of this tenant");
|
|
}
|
|
|
|
// Check for existing pending invitation
|
|
var existingInvitation = await invitationRepository.GetPendingByEmailAndTenantAsync(
|
|
request.Email,
|
|
tenantId,
|
|
cancellationToken);
|
|
|
|
if (existingInvitation != null)
|
|
throw new InvalidOperationException($"A pending invitation already exists for {request.Email} in this tenant");
|
|
|
|
// Generate secure token
|
|
var token = tokenService.GenerateToken();
|
|
var tokenHash = tokenService.HashToken(token);
|
|
|
|
// Create invitation
|
|
var invitation = Invitation.Create(
|
|
tenantId,
|
|
request.Email,
|
|
role,
|
|
tokenHash,
|
|
invitedBy);
|
|
|
|
await invitationRepository.AddAsync(invitation, cancellationToken);
|
|
|
|
// Send invitation email
|
|
var invitationLink = $"{request.BaseUrl}/accept-invitation?token={token}";
|
|
var htmlBody = templateService.RenderInvitationEmail(
|
|
recipientName: request.Email.Split('@')[0], // Use email prefix as fallback name
|
|
tenantName: tenant.Name.Value,
|
|
inviterName: inviter.FullName.Value,
|
|
invitationUrl: invitationLink);
|
|
|
|
var emailMessage = new EmailMessage(
|
|
To: request.Email,
|
|
Subject: $"You've been invited to join {tenant.Name.Value} on ColaFlow",
|
|
HtmlBody: htmlBody,
|
|
PlainTextBody: $"You've been invited to join {tenant.Name.Value}. Click here to accept: {invitationLink}");
|
|
|
|
var emailSuccess = await emailService.SendEmailAsync(emailMessage, cancellationToken);
|
|
|
|
if (!emailSuccess)
|
|
{
|
|
logger.LogWarning(
|
|
"Failed to send invitation email to {Email} for tenant {TenantId}",
|
|
request.Email,
|
|
request.TenantId);
|
|
}
|
|
else
|
|
{
|
|
logger.LogInformation(
|
|
"Invitation sent to {Email} for tenant {TenantId} with role {Role}",
|
|
request.Email,
|
|
request.TenantId,
|
|
role);
|
|
}
|
|
|
|
return invitation.Id;
|
|
}
|
|
}
|