From 22ac2889af6da8971fa74b156df88755433a7c8b Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 21 Jul 2026 12:28:30 +0200 Subject: [PATCH] feat(application): Add AddAnnotationCommand with handler and validator - Create AddAnnotationCommand (Command/Handler/Validator merged in single file) - Use primary constructors for handler (IPdfProcessor dependency) - FluentValidation rules: stream required, pageNumber > 0, content for FreeText/StickyNote - Validate textMarkupStyle required for TextMarkup annotations - Validate color format (6-digit hex) and rectangle coordinates (X2>X1, Y2>Y1) --- .../AddAnnotation/AddAnnotationCommand.cs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 DocumentOperator.Application/AddAnnotation/AddAnnotationCommand.cs diff --git a/DocumentOperator.Application/AddAnnotation/AddAnnotationCommand.cs b/DocumentOperator.Application/AddAnnotation/AddAnnotationCommand.cs new file mode 100644 index 0000000..17b28e8 --- /dev/null +++ b/DocumentOperator.Application/AddAnnotation/AddAnnotationCommand.cs @@ -0,0 +1,76 @@ +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; } +} + +/// +/// 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); + } +} + +/// +/// 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)"); + } +}