Add workflow and document APIs, middleware, and Swagger

Introduced `CloseWorkflowRequest` for closing workflows.
Added `DocumentsController` for document downloads and
`WorkflowsController` for workflow retrieval and closing.

Implemented `ExceptionHandlingMiddleware` for global exception
handling with structured API responses.

Enhanced Swagger configuration with metadata and production
support. Added `Scalar.AspNetCore` for API reference mapping.

Updated project dependencies and improved API documentation.
This commit is contained in:
2026-10-10 13:50:26 +02:00
parent b44be4f9e2
commit 3126f025be
6 changed files with 227 additions and 2 deletions

View File

@@ -0,0 +1,68 @@
using DigitalData.TaskFlow.API.Contracts.Workflows;
using DigitalData.TaskFlow.Application.Features.Workflows.Commands.CloseWorkflow;
using DigitalData.TaskFlow.Application.Features.Workflows.Queries.GetWorkflows;
using DigitalData.TaskFlow.Domain.Closing;
using DigitalData.TaskFlow.Domain.Common;
using DigitalData.TaskFlow.Domain.Workflows;
using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace DigitalData.TaskFlow.API.Controllers;
/// <summary>
/// Provides workflow read and close endpoints.
/// </summary>
[ApiController]
[Route("api/workflows")]
public sealed class WorkflowsController(ISender sender) : ControllerBase
{
/// <summary>
/// Returns open workflows for a WISAG user identifier.
/// </summary>
/// <param name="wisagId">WISAG user identifier.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Workflow response envelope grouped by source.</returns>
[HttpGet("{wisagId:int}")]
[ProducesResponseType(typeof(ApiEnvelope<WorkflowSourcesResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiEnvelope<object>), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ApiEnvelope<object>), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<ApiEnvelope<WorkflowSourcesResponse>>> GetAsync(
[FromRoute] int wisagId,
CancellationToken cancellationToken)
{
var response = await sender.Send(new GetWorkflowsQuery(wisagId), cancellationToken);
return Ok(response);
}
/// <summary>
/// Applies a closing decision to a workflow process.
/// </summary>
/// <param name="wisagId">WISAG user identifier.</param>
/// <param name="source">Source system name.</param>
/// <param name="processId">Process identifier.</param>
/// <param name="request">Closing request payload.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Close result envelope.</returns>
[HttpPost("{wisagId:int}/{source}/{processId:int}/closing")]
[ProducesResponseType(typeof(ApiEnvelope<CloseWorkflowResult>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiEnvelope<object>), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ApiEnvelope<object>), StatusCodes.Status500InternalServerError)]
public async Task<ActionResult<ApiEnvelope<CloseWorkflowResult>>> CloseAsync(
[FromRoute] int wisagId,
[FromRoute] string source,
[FromRoute] int processId,
[FromBody] CloseWorkflowRequest request,
CancellationToken cancellationToken)
{
var command = new CloseWorkflowCommand(
wisagId,
source,
processId,
request.ClosingMethodId,
request.Comment,
request.RejectionReasonId);
var response = await sender.Send(command, cancellationToken);
return Ok(response);
}
}