Add project files.

This commit is contained in:
Sergio Matias Urquin
2025-04-29 18:44:41 -06:00
parent c4ec91852f
commit b2635193dc
133 changed files with 4100 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
using Asp.Versioning;
using Core.Blueprint.Application.UsesCases.BlobStorage.Ports;
using Core.Blueprint.Application.UsesCases.KeyVault.Input;
using Lib.Architecture.BuildingBlocks;
using Microsoft.AspNetCore.Mvc;
namespace Core.Cerberos.Service.API.Controllers
{
/// <summary>
/// Handles all services and business rules related to <see cref="MStorageController"/>.
/// </summary>
[ApiVersion("1.0")]
[Route("api/v{api-version:apiVersion}/[controller]")]
[Produces("application/json")]
[ApiController]
public class BlobStorageController : ControllerBase
{
private readonly IComponentHandler<UploadBlobRequest> uploadBlobHandler;
private readonly IComponentHandler<GetBlobListRequest> getBlobListHandler;
private readonly IComponentHandler<DownloadBlobRequest> downloadBlobHandler;
private readonly IComponentHandler<DeleteBlobRequest> deleteBlobHandler;
private readonly IStoragePort port;
/// <summary>
/// Handles all services and business rules related to <see cref="MStorageController"/>.
/// </summary>
public BlobStorageController(
IComponentHandler<UploadBlobRequest> uploadBlobHandler,
IComponentHandler<GetBlobListRequest> getBlobListHandler,
IComponentHandler<DownloadBlobRequest> downloadBlobHandler,
IComponentHandler<DeleteBlobRequest> deleteBlobHandler,
IStoragePort port
)
{
this.uploadBlobHandler = uploadBlobHandler;
this.getBlobListHandler = getBlobListHandler;
this.downloadBlobHandler = downloadBlobHandler;
this.deleteBlobHandler = deleteBlobHandler;
this.port = port;
}
/// <summary>
/// Uploads a new blob.
/// </summary>
[HttpPost("UploadBlob")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> UploadBlobAsync([FromBody] UploadBlobRequest newBlob, CancellationToken cancellationToken = default)
{
await uploadBlobHandler.ExecuteAsync(newBlob, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Gets all blobs into the container.
/// </summary>
[HttpGet]
[Route("GetBlobList")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> GetBlobList([FromQuery] string? prefix, CancellationToken cancellationToken)
{
await getBlobListHandler.ExecuteAsync(new GetBlobListRequest { Prefix = prefix }, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Downloads a blob by name.
/// </summary>
[HttpPost("DownloadBlob")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> DownloadBlobAsync([FromBody] DownloadBlobRequest request, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(request.BlobName)) { return BadRequest("Invalid blob name"); }
await downloadBlobHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Deletes a blob by name.
/// </summary>
[HttpDelete]
[Route("DeleteBlob")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> DeleteBlobAsync([FromBody] DeleteBlobRequest request,
CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(request.BlobName)) { return BadRequest("Invalid blob name"); }
await deleteBlobHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
}
}

View File

@@ -0,0 +1,124 @@
using Asp.Versioning;
using Core.Blueprint.Application.UsesCases.KeyVault.Input;
using Core.Blueprint.Application.UsesCases.KeyVault.Ports;
using Lib.Architecture.BuildingBlocks;
using Microsoft.AspNetCore.Mvc;
namespace Core.Cerberos.Service.API.Controllers
{
/// <summary>
/// Handles all services and business rules related to <see cref="MKeyVaultController"/>.
/// </summary>
[ApiVersion("1.0")]
[Route("api/v{api-version:apiVersion}/[controller]")]
[Produces("application/json")]
[ApiController]
public class KeyVaultController : ControllerBase
{
private readonly IComponentHandler<CreateSecretRequest> createSecretHandler;
private readonly IComponentHandler<GetSecretRequest> getSecretHandler;
private readonly IComponentHandler<UpdateSecretRequest> updateSecretHandler;
private readonly IComponentHandler<DeleteSecretRequest> deleteSecretHandler;
private readonly IKeyVaultPort port;
/// <summary>
/// Handles all services and business rules related to <see cref="MKeyVaultController"/>.
/// </summary>
public KeyVaultController(
IComponentHandler<GetSecretRequest> getSecretHandler,
IComponentHandler<CreateSecretRequest> createSecretHandler,
IComponentHandler<UpdateSecretRequest> updateSecretHandler,
IComponentHandler<DeleteSecretRequest> deleteSecretHandler,
IKeyVaultPort port
)
{
this.createSecretHandler = createSecretHandler;
this.updateSecretHandler = updateSecretHandler;
this.deleteSecretHandler = deleteSecretHandler;
this.getSecretHandler = getSecretHandler;
this.port = port;
}
/// <summary>
/// Creates a new secret.
/// </summary>
[HttpPost("CreateSecret")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateSecretAsync([FromBody] CreateSecretRequest newSecret, CancellationToken cancellationToken = default)
{
await createSecretHandler.ExecuteAsync(newSecret, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Gets the secret by name.
/// </summary>
[HttpPost]
[Route("GetSecretByName")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> GetSecretByName([FromBody] GetSecretRequest request, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(request.Name)) { return BadRequest("Invalid secret name"); }
await getSecretHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Updates a full secret by identifier.
/// </summary>
[HttpPut("UpdateSecret")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> UpdateSecretAsync([FromBody] UpdateSecretRequest request, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(request.Name)) { return BadRequest("Invalid secret name"); }
if (string.IsNullOrEmpty(request.Value)) { return BadRequest("Invalid secret value"); }
await updateSecretHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Deletes a secret by name.
/// </summary>
[HttpDelete]
[Route("DeleteSecret")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> DeleteSecretAsync([FromBody] DeleteSecretRequest request,
CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(request.Name)) { return BadRequest("Invalid secret name"); }
await deleteSecretHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
}
}

View File

@@ -0,0 +1,142 @@
using Asp.Versioning;
using Core.Blueprint.Application.UsesCases.Mongo.Input;
using Core.Blueprint.Application.UsesCases.Mongo.Ports;
using Lib.Architecture.BuildingBlocks;
using Microsoft.AspNetCore.Mvc;
namespace Core.Cerberos.Service.API.Controllers
{
/// <summary>
/// Handles all services and business rules related to <see cref="MongoBlueprintController"/>.
/// </summary>
[ApiVersion("1.0")]
[Route("api/v{api-version:apiVersion}/[controller]")]
[Produces("application/json")]
[ApiController]
public class MongoBlueprintController : ControllerBase
{
private readonly IComponentHandler<CreateBlueprintRequest> createBlueprintHandler;
private readonly IComponentHandler<GetAllBlueprintsRequest> getAllBlueprintsHandler;
private readonly IComponentHandler<GetBlueprintRequest> getBlueprintHandler;
private readonly IComponentHandler<UpdateBlueprintRequest> updateBlueprintHandler;
private readonly IComponentHandler<DeleteBlueprintRequest> deleteBlueprintHandler;
private readonly IMongoPort port;
/// <summary>
/// Handles all services and business rules related to <see cref="MongoBlueprintController"/>.
/// </summary>
public MongoBlueprintController(
IComponentHandler<GetBlueprintRequest> getBlueprintHandler,
IComponentHandler<GetAllBlueprintsRequest> getAllBlueprintsHandler,
IComponentHandler<CreateBlueprintRequest> createBlueprintHandler,
IComponentHandler<UpdateBlueprintRequest> updateBlueprintHandler,
IComponentHandler<DeleteBlueprintRequest> deleteBlueprintHandler,
IMongoPort port
)
{
this.createBlueprintHandler = createBlueprintHandler;
this.updateBlueprintHandler = updateBlueprintHandler;
this.deleteBlueprintHandler = deleteBlueprintHandler;
this.getAllBlueprintsHandler = getAllBlueprintsHandler;
this.getBlueprintHandler = getBlueprintHandler;
this.port = port;
}
/// <summary>
/// Creates a new blueprint.
/// </summary>
[HttpPost("Create")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateBlueprintAsync([FromBody] CreateBlueprintRequest newBlueprint, CancellationToken cancellationToken = default)
{
await createBlueprintHandler.ExecuteAsync(newBlueprint, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Gets all blueprints.
/// </summary>
[HttpGet("GetAll")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> GetAllBlueprintsAsync(CancellationToken cancellationToken)
{
await getAllBlueprintsHandler.ExecuteAsync(new GetAllBlueprintsRequest { }, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Gets the blueprint by identifier.
/// </summary>
[HttpPost]
[Route("GetById")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> GetBlueprintById([FromBody] GetBlueprintRequest request, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(request._Id)) { return BadRequest("Invalid blueprint identifier"); }
await getBlueprintHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Updates a full blueprint by identifier.
/// </summary>
[HttpPut("Update")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> UpdateBlueprintAsync([FromBody] UpdateBlueprintRequest request, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(request._Id)) { return BadRequest("Invalid blueprint identifier"); }
await updateBlueprintHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Deletes a blueprint.
/// </summary>
[HttpDelete]
[Route("Delete")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> DeleteBlueprintAsync([FromBody] DeleteBlueprintRequest request,
CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(request._Id)) { return BadRequest("Invalid blueprint identifier"); }
await deleteBlueprintHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
}
}

View File

@@ -0,0 +1,142 @@
using Asp.Versioning;
using Core.Blueprint.Application.UsesCases.SQL.Input;
using Core.Blueprint.Application.UsesCases.SQL.Ports;
using Lib.Architecture.BuildingBlocks;
using Microsoft.AspNetCore.Mvc;
namespace Core.Cerberos.Service.API.Controllers
{
/// <summary>
/// Handles all services and business rules related to <see cref="SQLUserProjectController"/>.
/// </summary>
[ApiVersion("1.0")]
[Route("api/v{api-version:apiVersion}/[controller]")]
[Produces("application/json")]
[ApiController]
public class SQLUserProjectController : ControllerBase
{
private readonly IComponentHandler<CreateUserProjectRequest> createUserProjectHandler;
private readonly IComponentHandler<GetAllUserProjectsRequest> getAllUserProjectsHandler;
private readonly IComponentHandler<GetUserProjectRequest> getUserProjectHandler;
private readonly IComponentHandler<UpdateUserProjectRequest> updateUserProjectHandler;
private readonly IComponentHandler<DeleteUserProjectRequest> deleteUserProjectStatusHandler;
private readonly ISQLPort port;
/// <summary>
/// Handles all services and business rules related to <see cref="SQLUserProjectController"/>.
/// </summary>
public SQLUserProjectController(
IComponentHandler<GetUserProjectRequest> getUserProjectHandler,
IComponentHandler<GetAllUserProjectsRequest> getAllUserProjectsHandler,
IComponentHandler<CreateUserProjectRequest> createUserProjectHandler,
IComponentHandler<UpdateUserProjectRequest> updateUserProjectHandler,
IComponentHandler<DeleteUserProjectRequest> deleteUserProjectStatusHandler,
ISQLPort port
)
{
this.createUserProjectHandler = createUserProjectHandler;
this.updateUserProjectHandler = updateUserProjectHandler;
this.deleteUserProjectStatusHandler = deleteUserProjectStatusHandler;
this.getAllUserProjectsHandler = getAllUserProjectsHandler;
this.getUserProjectHandler = getUserProjectHandler;
this.port = port;
}
/// <summary>
/// Creates a new UserProject.
/// </summary>
[HttpPost("Create")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateUserProjectAsync([FromBody] CreateUserProjectRequest newUserProject, CancellationToken cancellationToken = default)
{
await createUserProjectHandler.ExecuteAsync(newUserProject, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Gets all UserProjects.
/// </summary>
[HttpGet("GetAll")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> GetAllUserProjectsAsync(CancellationToken cancellationToken)
{
await getAllUserProjectsHandler.ExecuteAsync(new GetAllUserProjectsRequest { }, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Gets the UserProject by identifier.
/// </summary>
[HttpPost]
[Route("GetById")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> GetUserProjectById([FromBody] GetUserProjectRequest request, CancellationToken cancellationToken)
{
if (request.Id <= 0) { return BadRequest("Invalid UserProject identifier"); }
await getUserProjectHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Updates a full UserProject by identifier.
/// </summary>
[HttpPut("Update")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> UpdateUserProjectAsync([FromBody] UpdateUserProjectRequest request, CancellationToken cancellationToken = default)
{
if (request.Id <= 0) { return BadRequest("Invalid UserProject identifier"); }
await updateUserProjectHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
/// <summary>
/// Deletes a UserProject.
/// </summary>
[HttpDelete]
[Route("Delete")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status412PreconditionFailed)]
[ProducesResponseType(typeof(Notification), StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> DeleteUserProjectStatusAsync([FromBody] DeleteUserProjectRequest request,
CancellationToken cancellationToken)
{
if (request.Id <= 0) { return BadRequest("Invalid UserProject identifier"); }
await deleteUserProjectStatusHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return port.ViewModel;
}
}
}