using DocumentOperator.Application.Common.Interfaces; using DocumentOperator.Domain.Models.ValueObjects; using FluentValidation; using MediatR; namespace DocumentOperator.Application.AddAnnotation; /// /// Command to add an annotation to a PDF document. /// public record AddAnnotationCommand : IRequest { public required Stream PdfStream { get; init; } public required AnnotationType AnnotationType { get; init; } public required int PageNumber { get; init; } public required (double X1, double Y1, double X2, double Y2) Rectangle { get; init; } public string? Content { get; init; } public string? Author { get; init; } public string? Color { get; init; } public TextMarkupStyle? TextMarkupStyle { get; init; } public AnnotationOrigin Origin { get; init; } = AnnotationOrigin.BottomLeft; } /// /// Handler for AddAnnotationCommand. /// public class AddAnnotationHandler(IPdfProcessor pdfProcessor) : IRequestHandler { public async Task Handle(AddAnnotationCommand request, CancellationToken cancellationToken) { return await pdfProcessor.AddAnnotationAsync( request.PdfStream, request.AnnotationType, request.PageNumber, request.Rectangle, request.Content, request.Author, request.Color, request.TextMarkupStyle, request.Origin); } } /// /// Validator for AddAnnotationCommand. /// public class AddAnnotationValidator : AbstractValidator { public AddAnnotationValidator() { RuleFor(x => x.PdfStream) .NotNull() .WithMessage("PDF stream is required"); RuleFor(x => x.PageNumber) .GreaterThan(0) .WithMessage("Page number must be greater than 0"); RuleFor(x => x.Content) .NotEmpty() .When(x => x.AnnotationType == AnnotationType.FreeText || x.AnnotationType == AnnotationType.StickyNote) .WithMessage("Content is required for FreeText and StickyNote annotations"); RuleFor(x => x.TextMarkupStyle) .NotNull() .When(x => x.AnnotationType == AnnotationType.TextMarkup) .WithMessage("TextMarkupStyle is required for TextMarkup annotations"); RuleFor(x => x.Color) .Matches("^[0-9A-Fa-f]{6}$") .When(x => !string.IsNullOrWhiteSpace(x.Color)) .WithMessage("Color must be a 6-digit hex value (e.g., 'FF0000' for red)"); RuleFor(x => x.Rectangle) .Must(r => r.X2 > r.X1 && r.Y2 > r.Y1) .WithMessage("Rectangle coordinates must define a valid area (X2 > X1 and Y2 > Y1)"); } }