diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Exceptions/ApplicationAlreadyProcessedException.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Exceptions/ApplicationAlreadyProcessedException.cs new file mode 100644 index 0000000..47da53e --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Exceptions/ApplicationAlreadyProcessedException.cs @@ -0,0 +1,6 @@ +using ApplicationLayer.GeneralExceptions; + +namespace ApplicationLayer.Services.VisaApplications.Exceptions +{ + public class ApplicationAlreadyProcessedException() : ApiException("This application already processed or closed by applicant."); +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs index 03eefe0..1f5ed5e 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs @@ -12,8 +12,10 @@ public interface IVisaApplicationRequestsHandler Task> GetForApplicantAsync(Guid userId, CancellationToken cancellationToken); /// Creates application for applicant with specific user identifier - Task HandleCreateRequest(Guid userId, VisaApplicationCreateRequest request, CancellationToken cancellationToken); + Task HandleCreateRequestAsync(Guid userId, VisaApplicationCreateRequest request, CancellationToken cancellationToken); /// Sets application status to closed - Task HandleCloseRequest(Guid userId, Guid applicationId, CancellationToken cancellationToken); + Task HandleCloseRequestAsync(Guid userId, Guid applicationId, CancellationToken cancellationToken); + + Task SetApplicationStatusFromAuthorityAsync(Guid applicationId, AuthorityRequestStatuses status, CancellationToken cancellationToken); } diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs index 428f045..88a6b27 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs @@ -1,6 +1,7 @@ using ApplicationLayer.InfrastructureServicesInterfaces; using ApplicationLayer.Services.Applicants.Models; using ApplicationLayer.Services.Applicants.NeededServices; +using ApplicationLayer.Services.VisaApplications.Exceptions; using ApplicationLayer.Services.VisaApplications.Models; using ApplicationLayer.Services.VisaApplications.NeededServices; using ApplicationLayer.Services.VisaApplications.Requests; @@ -87,7 +88,7 @@ public class VisaApplicationRequestsHandler( .ToList(); } - public async Task HandleCreateRequest(Guid userId, VisaApplicationCreateRequest request, CancellationToken cancellationToken) + public async Task HandleCreateRequestAsync(Guid userId, VisaApplicationCreateRequest request, CancellationToken cancellationToken) { //TODO mapper @@ -114,7 +115,7 @@ public class VisaApplicationRequestsHandler( await unitOfWork.SaveAsync(cancellationToken); } - async Task IVisaApplicationRequestsHandler.HandleCloseRequest(Guid userId, Guid applicationId, CancellationToken cancellationToken) + async Task IVisaApplicationRequestsHandler.HandleCloseRequestAsync(Guid userId, Guid applicationId, CancellationToken cancellationToken) { var applicantId = await applicants.GetApplicantIdByUserId(userId, cancellationToken); var application = await applications.GetByApplicantAndApplicationIdAsync(applicantId, applicationId, cancellationToken); @@ -124,4 +125,30 @@ public class VisaApplicationRequestsHandler( await unitOfWork.SaveAsync(cancellationToken); } + + async Task IVisaApplicationRequestsHandler.SetApplicationStatusFromAuthorityAsync( + Guid applicationId, + AuthorityRequestStatuses status, + CancellationToken cancellationToken) + { + var application = await applications.GetByIdAsync(applicationId, cancellationToken); + if (application.Status != ApplicationStatus.Pending) + { + //todo refactor exceptions + throw new ApplicationAlreadyProcessedException(); + } + + //todo mapper + ApplicationStatus statusToSet = status switch + { + AuthorityRequestStatuses.Approved => ApplicationStatus.Approved, + AuthorityRequestStatuses.Rejected => ApplicationStatus.Rejected, + _ => throw new ArgumentOutOfRangeException(nameof(status), status, null) + }; + + application.Status = statusToSet; + await applications.UpdateAsync(application, cancellationToken); + + await unitOfWork.SaveAsync(cancellationToken); + } } diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/AuthorityRequestStatuses.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/AuthorityRequestStatuses.cs new file mode 100644 index 0000000..93f2a93 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/AuthorityRequestStatuses.cs @@ -0,0 +1,8 @@ +namespace ApplicationLayer.Services.VisaApplications.Models +{ + public enum AuthorityRequestStatuses + { + Approved, + Rejected + } +} diff --git a/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs b/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs index 0236f1b..ac0201b 100644 --- a/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs +++ b/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs @@ -35,7 +35,7 @@ namespace SchengenVisaApi.Controllers [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [Route("authority")] + [Route("authorities")] [Authorize(policy: PolicyConstants.AdminPolicy)] public async Task RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken) { @@ -60,7 +60,7 @@ namespace SchengenVisaApi.Controllers [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [Route("authority")] + [Route("authorities")] [Authorize(policy: PolicyConstants.AdminPolicy)] public async Task GetAuthorityAccounts(CancellationToken cancellationToken) { @@ -75,7 +75,7 @@ namespace SchengenVisaApi.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [Route("authority/{authorityAccountId:guid}")] + [Route("authorities/{authorityAccountId:guid}")] [Authorize(policy: PolicyConstants.AdminPolicy)] //todo replace args with ChangeAuthorityAuthDataRequest or something public async Task ChangeAuthorityAuthData(Guid authorityAccountId, RegisterRequest authData, CancellationToken cancellationToken) @@ -91,7 +91,7 @@ namespace SchengenVisaApi.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [Route("authority/{authorityAccountId:guid}")] + [Route("authorities/{authorityAccountId:guid}")] [Authorize(policy: PolicyConstants.AdminPolicy)] public async Task RemoveAuthorityAccount(Guid authorityAccountId, CancellationToken cancellationToken) { diff --git a/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs b/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs index 61dcd76..d47271d 100644 --- a/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs +++ b/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs @@ -9,7 +9,7 @@ namespace SchengenVisaApi.Controllers; /// Controller for [ApiController] -[Route("visaApplication")] +[Route("visaApplications")] public class VisaApplicationController(IVisaApplicationRequestsHandler visaApplicationRequestsHandler) : VisaApiControllerBase { //todo should return model @@ -53,7 +53,7 @@ public class VisaApplicationController(IVisaApplicationRequestsHandler visaAppli public async Task Create(VisaApplicationCreateRequest request, CancellationToken cancellationToken) { var userId = GetUserId(); - await visaApplicationRequestsHandler.HandleCreateRequest(userId, request, cancellationToken); + await visaApplicationRequestsHandler.HandleCreateRequestAsync(userId, request, cancellationToken); return Ok(); } @@ -69,7 +69,22 @@ public class VisaApplicationController(IVisaApplicationRequestsHandler visaAppli public async Task CloseApplication(Guid applicationId, CancellationToken cancellationToken) { var userId = GetUserId(); - await visaApplicationRequestsHandler.HandleCloseRequest(userId, applicationId, cancellationToken); + await visaApplicationRequestsHandler.HandleCloseRequestAsync(userId, applicationId, cancellationToken); + return Ok(); + } + + /// Allows approving authorities approve or reject applications + /// Accessible only for authorities + [HttpPatch] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Authorize(policy: PolicyConstants.ApprovingAuthorityPolicy)] + [Route("approving/{applicationId:guid}")] + public async Task SetStatusFromAuthority(Guid applicationId, AuthorityRequestStatuses status, CancellationToken cancellationToken) + { + await visaApplicationRequestsHandler.SetApplicationStatusFromAuthorityAsync(applicationId, status, cancellationToken); return Ok(); } } diff --git a/SchengenVisaApi/SchengenVisaApi/ExceptionFilters/GlobalExceptionsFilter.cs b/SchengenVisaApi/SchengenVisaApi/ExceptionFilters/GlobalExceptionsFilter.cs index ae7311c..4778f64 100644 --- a/SchengenVisaApi/SchengenVisaApi/ExceptionFilters/GlobalExceptionsFilter.cs +++ b/SchengenVisaApi/SchengenVisaApi/ExceptionFilters/GlobalExceptionsFilter.cs @@ -1,6 +1,7 @@ using ApplicationLayer.GeneralExceptions; using ApplicationLayer.Services.AuthServices.LoginService.Exceptions; using ApplicationLayer.Services.GeneralExceptions; +using ApplicationLayer.Services.VisaApplications.Exceptions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -35,6 +36,11 @@ namespace SchengenVisaApi.ExceptionFilters problemDetails.Title = "Already exists"; problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.8"; break; + case ApplicationAlreadyProcessedException: + problemDetails.Status = StatusCodes.Status409Conflict; + problemDetails.Title = "Already processed"; + problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.8"; + break; default: problemDetails.Status = StatusCodes.Status400BadRequest; problemDetails.Title = "Bad request";