diff --git a/src/core/DigitalData.TaskFlow.Application/Common/Behaviors/ValidationBehavior.cs b/src/core/DigitalData.TaskFlow.Application/Common/Behaviors/ValidationBehavior.cs
new file mode 100644
index 0000000..6af7d8d
--- /dev/null
+++ b/src/core/DigitalData.TaskFlow.Application/Common/Behaviors/ValidationBehavior.cs
@@ -0,0 +1,48 @@
+using FluentValidation;
+using MediatR;
+
+namespace DigitalData.TaskFlow.Application.Common.Behaviors;
+
+///
+/// Runs FluentValidation validators before a MediatR request reaches its handler.
+///
+/// Request type.
+/// Response type.
+public sealed class ValidationBehavior(IEnumerable> validators)
+ : IPipelineBehavior
+ where TRequest : notnull
+{
+ ///
+ /// Validates the request and continues the pipeline when validation succeeds.
+ ///
+ /// Current request instance.
+ /// Next pipeline delegate.
+ /// Cancellation token.
+ /// Handler response when validation passes.
+ /// Thrown when one or more validation errors are found.
+ public async Task Handle(
+ TRequest request,
+ RequestHandlerDelegate next,
+ CancellationToken cancellationToken)
+ {
+ if (!validators.Any())
+ {
+ return await next(cancellationToken);
+ }
+
+ var context = new ValidationContext(request);
+ var validationResults = await Task.WhenAll(validators.Select(v => v.ValidateAsync(context, cancellationToken)));
+
+ var failures = validationResults
+ .SelectMany(r => r.Errors)
+ .Where(f => f is not null)
+ .ToList();
+
+ if (failures.Count != 0)
+ {
+ throw new ValidationException(failures);
+ }
+
+ return await next(cancellationToken);
+ }
+}
diff --git a/src/core/DigitalData.TaskFlow.Application/Common/Interfaces/IDocumentRepository.cs b/src/core/DigitalData.TaskFlow.Application/Common/Interfaces/IDocumentRepository.cs
new file mode 100644
index 0000000..058c4fd
--- /dev/null
+++ b/src/core/DigitalData.TaskFlow.Application/Common/Interfaces/IDocumentRepository.cs
@@ -0,0 +1,17 @@
+using DigitalData.TaskFlow.Domain.Documents;
+
+namespace DigitalData.TaskFlow.Application.Common.Interfaces;
+
+///
+/// Defines document-specific data access operations.
+///
+public interface IDocumentRepository : IRepository
+{
+ ///
+ /// Gets document binary content for the given document identifier.
+ ///
+ /// Document identifier.
+ /// Cancellation token.
+ /// Binary document payload.
+ Task GetAsync(string id, CancellationToken cancellationToken);
+}
diff --git a/src/core/DigitalData.TaskFlow.Application/Common/Interfaces/IRepository.cs b/src/core/DigitalData.TaskFlow.Application/Common/Interfaces/IRepository.cs
new file mode 100644
index 0000000..589ec36
--- /dev/null
+++ b/src/core/DigitalData.TaskFlow.Application/Common/Interfaces/IRepository.cs
@@ -0,0 +1,8 @@
+namespace DigitalData.TaskFlow.Application.Common.Interfaces;
+
+///
+/// Defines a marker contract for repository abstractions in the application layer.
+///
+public interface IRepository
+{
+}
diff --git a/src/core/DigitalData.TaskFlow.Application/Common/Interfaces/IWorkflowRepository.cs b/src/core/DigitalData.TaskFlow.Application/Common/Interfaces/IWorkflowRepository.cs
new file mode 100644
index 0000000..87fb6ec
--- /dev/null
+++ b/src/core/DigitalData.TaskFlow.Application/Common/Interfaces/IWorkflowRepository.cs
@@ -0,0 +1,34 @@
+using DigitalData.TaskFlow.Domain.Closing;
+using DigitalData.TaskFlow.Domain.Workflows;
+
+namespace DigitalData.TaskFlow.Application.Common.Interfaces;
+
+///
+/// Defines workflow-specific data access operations for reading and closing workflow items.
+///
+public interface IWorkflowRepository : IRepository
+{
+ ///
+ /// Gets grouped workflow data for the given WISAG user identifier.
+ ///
+ /// WISAG user identifier.
+ /// Cancellation token.
+ /// Workflow data grouped by source.
+ Task GetAsync(int wisagId, CancellationToken cancellationToken);
+
+ ///
+ /// Applies a closing decision to a workflow process.
+ ///
+ /// WISAG user identifier.
+ /// Source system name.
+ /// Process identifier.
+ /// Closing decision payload.
+ /// Cancellation token.
+ /// Closing operation result.
+ Task CloseAsync(
+ int wisagId,
+ string source,
+ int processId,
+ CloseWorkflowDecision decision,
+ CancellationToken cancellationToken);
+}
diff --git a/src/core/DigitalData.TaskFlow.Application/Common/Mappings/ApplicationMappingProfile.cs b/src/core/DigitalData.TaskFlow.Application/Common/Mappings/ApplicationMappingProfile.cs
new file mode 100644
index 0000000..8797642
--- /dev/null
+++ b/src/core/DigitalData.TaskFlow.Application/Common/Mappings/ApplicationMappingProfile.cs
@@ -0,0 +1,16 @@
+using AutoMapper;
+
+namespace DigitalData.TaskFlow.Application.Common.Mappings;
+
+///
+/// Defines AutoMapper configuration for application-level mappings.
+///
+public sealed class ApplicationMappingProfile : Profile
+{
+ ///
+ /// Initializes mapping configuration.
+ ///
+ public ApplicationMappingProfile()
+ {
+ }
+}
diff --git a/src/core/DigitalData.TaskFlow.Application/DependencyInjection.cs b/src/core/DigitalData.TaskFlow.Application/DependencyInjection.cs
index 840ce5e..59c36d5 100644
--- a/src/core/DigitalData.TaskFlow.Application/DependencyInjection.cs
+++ b/src/core/DigitalData.TaskFlow.Application/DependencyInjection.cs
@@ -4,6 +4,7 @@ using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
+using DigitalData.TaskFlow.Application.Common.Behaviors;
namespace DigitalData.TaskFlow.Application;
@@ -30,6 +31,7 @@ public static class DependencyInjection
});
services.AddValidatorsFromAssembly(typeof(DependencyInjection).Assembly);
+ services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
services.AddAutoMapper(_ => { }, Assembly.GetExecutingAssembly());
return services;