Files
ColaFlow/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Application/Commands/InviteUser/InviteUserCommandHandler.cs
Yaojia Wang 63ff1a9914
Some checks failed
Code Coverage / Generate Coverage Report (push) Has been cancelled
Tests / Run Tests (9.0.x) (push) Has been cancelled
Tests / Docker Build Test (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Clean up
2025-11-09 18:40:36 +01:00

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;
}
}