using System; using Svrnty.Sample.Events; using Svrnty.CQRS.Events.Abstractions.EventHandlers; using Svrnty.CQRS.Events.Abstractions.Models; using System.Threading; using System.Threading.Tasks; using FluentValidation; using Svrnty.CQRS.Events.Abstractions; namespace Svrnty.Sample.Workflows; // ============================================================================ // STEP 1: Invite User Command // ============================================================================ /// /// Command to invite a user via email. /// This is the first step in a multi-step workflow. /// public record InviteUserCommand { public required string Email { get; init; } public required string InviterName { get; init; } } public class InviteUserCommandValidator : AbstractValidator { public InviteUserCommandValidator() { RuleFor(x => x.Email) .NotEmpty() .EmailAddress() .WithMessage("Valid email is required"); RuleFor(x => x.InviterName) .NotEmpty() .WithMessage("Inviter name is required"); } } /// /// Handler for inviting a user. /// Phase 1: Creates a new workflow instance for each invitation. /// /// /// Future phases will support workflow continuation where Accept/Decline commands /// can reference the same workflow instance using the workflow ID. /// public class InviteUserCommandHandler : ICommandHandlerWithWorkflow { public async Task HandleAsync( InviteUserCommand command, InvitationWorkflow workflow, CancellationToken cancellationToken = default) { // Generate invitation ID var invitationId = Guid.NewGuid().ToString(); // Emit event via workflow // Framework automatically assigns workflow.Id as CorrelationId workflow.EmitInvited(new UserInvitedEvent { InvitationId = invitationId, Email = command.Email, InviterName = command.InviterName }); // Return invitation ID (client can use this to accept/decline) // Note: workflow.Id is the correlation ID, but for Phase 1 they're independent return await Task.FromResult(invitationId); } } // ============================================================================ // STEP 2: Accept/Decline Invite Commands // ============================================================================ /// /// Command to accept a user invitation. /// Uses the same business data (email) as correlation key. /// public record AcceptInviteCommand { public required string InvitationId { get; init; } public required string Email { get; init; } // Used for correlation public required string Name { get; init; } } public class AcceptInviteCommandValidator : AbstractValidator { public AcceptInviteCommandValidator() { RuleFor(x => x.InvitationId) .NotEmpty() .WithMessage("Invitation ID is required"); RuleFor(x => x.Name) .NotEmpty() .WithMessage("Name is required"); } } /// /// Handler for accepting an invitation. /// Phase 1: Creates a new workflow instance for the acceptance. /// /// /// Future phases will support continuing the InvitationWorkflow from the invite command, /// so all events (invite + accept) share the same workflow/correlation ID. /// public class AcceptInviteCommandHandler : ICommandHandlerWithWorkflow { public async Task HandleAsync( AcceptInviteCommand command, InvitationWorkflow workflow, CancellationToken cancellationToken = default) { // Generate user ID var userId = new Random().Next(1000, 9999); // Emit acceptance event via workflow workflow.EmitAccepted(new UserInviteAcceptedEvent { InvitationId = command.InvitationId, UserId = userId, Name = command.Name }); return await Task.FromResult(userId); } } /// /// Command to decline a user invitation. /// public record DeclineInviteCommand { public required string InvitationId { get; init; } public string? Reason { get; init; } } /// /// Handler for declining an invitation. /// Phase 1: Creates a new workflow instance for the decline action. /// /// /// Future phases will support continuing the InvitationWorkflow from the invite command, /// so all events (invite + decline) share the same workflow/correlation ID. /// public class DeclineInviteCommandHandler : ICommandHandlerWithWorkflow { public async Task HandleAsync( DeclineInviteCommand command, InvitationWorkflow workflow, CancellationToken cancellationToken = default) { // Emit decline event via workflow workflow.EmitDeclined(new UserInviteDeclinedEvent { InvitationId = command.InvitationId, Reason = command.Reason }); await Task.CompletedTask; } } // ============================================================================ // Events for the Workflow // ============================================================================ public sealed record UserInvitedEvent : UserEvent { public required string InvitationId { get; init; } public required string Email { get; init; } public required string InviterName { get; init; } } public sealed record UserInviteAcceptedEvent : UserEvent { public required string InvitationId { get; init; } public required int UserId { get; init; } public required string Name { get; init; } } public sealed record UserInviteDeclinedEvent : UserEvent { public required string InvitationId { get; init; } public string? Reason { get; init; } }