diff --git a/SchengenVisaApi/ApplicationLayer/ApplicationLayer.csproj b/SchengenVisaApi/ApplicationLayer/ApplicationLayer.csproj index bc6298d..001de30 100644 --- a/SchengenVisaApi/ApplicationLayer/ApplicationLayer.csproj +++ b/SchengenVisaApi/ApplicationLayer/ApplicationLayer.csproj @@ -14,4 +14,8 @@ + + + + diff --git a/SchengenVisaApi/ApplicationLayer/Services/GeneralExceptions/EntityNotFoundByIdException.cs b/SchengenVisaApi/ApplicationLayer/Services/GeneralExceptions/EntityNotFoundByIdException.cs index d3155fe..d7d3e03 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/GeneralExceptions/EntityNotFoundByIdException.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/GeneralExceptions/EntityNotFoundByIdException.cs @@ -5,5 +5,5 @@ namespace ApplicationLayer.Services.GeneralExceptions; /// Exception to throw when entity not found /// Identifier of entity /// Type of entity -public class EntityNotFoundByIdException(Guid id) : EntityNotFoundException($"{typeof(T).Name} with id {id} not found.") +public class EntityNotFoundByIdException(Guid id) : EntityNotFoundException($"{typeof(T).Name} with id {id} not found.") where T : class, IEntity; diff --git a/SchengenVisaApi/ApplicationLayer/Services/GeneralExceptions/EntityNotFoundException.cs b/SchengenVisaApi/ApplicationLayer/Services/GeneralExceptions/EntityNotFoundException.cs index 4f4acca..c0f4867 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/GeneralExceptions/EntityNotFoundException.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/GeneralExceptions/EntityNotFoundException.cs @@ -1,9 +1,6 @@ using ApplicationLayer.GeneralExceptions; -using Domains; namespace ApplicationLayer.Services.GeneralExceptions; /// Exception to throw when entity not found -/// Not found entity type -public class EntityNotFoundException(string message) : ApiException(message) - where T : class, IEntity; +public class EntityNotFoundException(string message) : ApiException(message); diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs index e92e7d6..fcd2b53 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs @@ -14,4 +14,7 @@ public interface IVisaApplicationRequestsHandler /// Creates application for applicant with specific user identifier Task HandleCreateRequest(Guid userId, VisaApplicationCreateRequest request, CancellationToken cancellationToken); + + /// Sets application status to closed + Task HandleCloseRequest(Guid userId, Guid applicationId, CancellationToken cancellationToken); } diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs index 6997425..ab30d22 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs @@ -114,4 +114,15 @@ public class VisaApplicationRequestsHandler( await unitOfWork.SaveAsync(cancellationToken); } + + async Task IVisaApplicationRequestsHandler.HandleCloseRequest(Guid userId, Guid applicationId, CancellationToken cancellationToken) + { + var applicantId = await applicants.GetApplicantIdByUserId(userId, cancellationToken); + var application = await applications.GetByApplicantAndApplicationIdAsync(applicantId, applicationId, cancellationToken); + + application.Status = ApplicationStatus.Closed; + await applications.UpdateAsync(application, cancellationToken); + + await unitOfWork.SaveAsync(cancellationToken); + } } diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/NeededServices/IVisaApplicationsRepository.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/NeededServices/IVisaApplicationsRepository.cs index 8904a67..5e132a8 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/NeededServices/IVisaApplicationsRepository.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/NeededServices/IVisaApplicationsRepository.cs @@ -7,4 +7,7 @@ public interface IVisaApplicationsRepository : IGenericRepository> GetOfApplicantAsync(Guid applicantId, CancellationToken cancellationToken); + + /// Get specific application of specific user + Task GetByApplicantAndApplicationIdAsync(Guid applicantId, Guid applicationId, CancellationToken cancellationToken); } diff --git a/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/ApplicantsRepository.cs b/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/ApplicantsRepository.cs index a5a9ef5..64f4e7f 100644 --- a/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/ApplicantsRepository.cs +++ b/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/ApplicantsRepository.cs @@ -21,12 +21,12 @@ public sealed class ApplicantsRepository(IGenericReader reader, IGenericWriter w async Task IApplicantsRepository.FindByUserIdAsync(Guid userId, CancellationToken cancellationToken) { var result = await LoadDomain().SingleOrDefaultAsync(a => a.UserId == userId, cancellationToken); - return result ?? throw new ApplicantNotFoundByUserIdException(userId); + return result ?? throw new ApplicantNotFoundByUserIdException(); } async Task IApplicantsRepository.GetApplicantIdByUserId(Guid userId, CancellationToken cancellationToken) { var result = await base.LoadDomain().SingleOrDefaultAsync(a => a.UserId == userId, cancellationToken); - return result?.Id ?? throw new ApplicantNotFoundByUserIdException(userId); + return result?.Id ?? throw new ApplicantNotFoundByUserIdException(); } } diff --git a/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/Exceptions/ApplicantNotFoundByUserIdException.cs b/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/Exceptions/ApplicantNotFoundByUserIdException.cs index 52e7046..a45f9e8 100644 --- a/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/Exceptions/ApplicantNotFoundByUserIdException.cs +++ b/SchengenVisaApi/Infrastructure/Database/Applicants/Repositories/Exceptions/ApplicantNotFoundByUserIdException.cs @@ -3,8 +3,5 @@ using Domains.ApplicantDomain; namespace Infrastructure.Database.Applicants.Repositories.Exceptions { - public class ApplicantNotFoundByUserIdException(Guid id) : EntityNotFoundException("Applicant not found.") - { - public Guid UserId { get; private set; } = id; - } + public class ApplicantNotFoundByUserIdException() : EntityNotFoundException("Applicant not found."); } diff --git a/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/Exceptions/ApplicationNotFoundByApplicantAndApplicationIdException.cs b/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/Exceptions/ApplicationNotFoundByApplicantAndApplicationIdException.cs new file mode 100644 index 0000000..b8d1241 --- /dev/null +++ b/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/Exceptions/ApplicationNotFoundByApplicantAndApplicationIdException.cs @@ -0,0 +1,7 @@ +using ApplicationLayer.Services.GeneralExceptions; + +namespace Infrastructure.Database.VisaApplications.Repositories.Exceptions +{ + public class ApplicationNotFoundByApplicantAndApplicationIdException(Guid applicationId) + : EntityNotFoundException($"Application with id {applicationId} not found for authenticated user"); +} diff --git a/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs b/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs index cd20971..2a40dd7 100644 --- a/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs +++ b/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs @@ -1,6 +1,7 @@ using ApplicationLayer.Services.VisaApplications.NeededServices; using Domains.VisaApplicationDomain; using Infrastructure.Database.Generic; +using Infrastructure.Database.VisaApplications.Repositories.Exceptions; using Microsoft.EntityFrameworkCore; namespace Infrastructure.Database.VisaApplications.Repositories; @@ -16,4 +17,14 @@ public sealed class VisaApplicationsRepository(IGenericReader reader, IGenericWr async Task> IVisaApplicationsRepository.GetOfApplicantAsync(Guid applicantId, CancellationToken cancellationToken) => await LoadDomain().Where(va => va.ApplicantId == applicantId).ToListAsync(cancellationToken); + + async Task IVisaApplicationsRepository.GetByApplicantAndApplicationIdAsync( + Guid applicantId, + Guid applicationId, + CancellationToken cancellationToken) + { + var result = await LoadDomain() + .SingleOrDefaultAsync(va => va.Id == applicationId && va.ApplicantId == applicantId, cancellationToken); + return result ?? throw new ApplicationNotFoundByApplicantAndApplicationIdException(applicationId); + } } diff --git a/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs b/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs index 03f2a0e..50125fb 100644 --- a/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs +++ b/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs @@ -57,4 +57,20 @@ public class VisaApplicationController(IVisaApplicationRequestsHandler visaAppli await visaApplicationRequestsHandler.HandleCreateRequest(userId, request, cancellationToken); return Ok(); } + + /// Sets application status to closed + /// Accessible only for applicant + [HttpPatch] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Authorize(policy: PolicyConstants.ApplicantPolicy)] + [Route("{applicationId:guid}")] + public async Task CloseApplication(Guid applicationId, CancellationToken cancellationToken) + { + var userId = GetUserId(); + await visaApplicationRequestsHandler.HandleCloseRequest(userId, applicationId, cancellationToken); + return Ok(); + } } diff --git a/SchengenVisaApi/SchengenVisaApi/ExceptionFilters/GlobalExceptionsFilter.cs b/SchengenVisaApi/SchengenVisaApi/ExceptionFilters/GlobalExceptionsFilter.cs index 3bd38a6..d8e7c94 100644 --- a/SchengenVisaApi/SchengenVisaApi/ExceptionFilters/GlobalExceptionsFilter.cs +++ b/SchengenVisaApi/SchengenVisaApi/ExceptionFilters/GlobalExceptionsFilter.cs @@ -21,7 +21,7 @@ namespace SchengenVisaApi.ExceptionFilters problemDetails.Detail = exception.Message; switch (exception) { - case EntityNotFoundException: + case EntityNotFoundException: problemDetails.Status = StatusCodes.Status404NotFound; problemDetails.Title = "Requested entity not found"; problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4";