diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs index 6d9fdef..80cce41 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/IVisaApplicationRequestsHandler.cs @@ -6,7 +6,7 @@ namespace ApplicationLayer.Services.VisaApplications.Handlers; public interface IVisaApplicationRequestsHandler { /// Returns all applications for approving authorities - Task> GetAllAsync(CancellationToken cancellationToken); + Task> GetPendingAsync(CancellationToken cancellationToken); /// Returns all applications of one applicant Task> GetForApplicantAsync(CancellationToken cancellationToken); diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs index 63c3e25..4088eae 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs @@ -19,9 +19,9 @@ public class VisaApplicationRequestsHandler( IDateTimeProvider dateTimeProvider, IUserIdProvider userIdProvider) : IVisaApplicationRequestsHandler { - async Task> IVisaApplicationRequestsHandler.GetAllAsync(CancellationToken cancellationToken) + async Task> IVisaApplicationRequestsHandler.GetPendingAsync(CancellationToken cancellationToken) { - var applicationsList = await applications.GetAllAsync(cancellationToken); + var applicationsList = await applications.GetPendingApplicationsAsync(cancellationToken); var applicationModels = applicationsList .Select(a => MapVisaApplicationToModelForAuthorities(a, cancellationToken).Result) diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/NeededServices/IVisaApplicationsRepository.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/NeededServices/IVisaApplicationsRepository.cs index 5e132a8..270636d 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/NeededServices/IVisaApplicationsRepository.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/NeededServices/IVisaApplicationsRepository.cs @@ -10,4 +10,7 @@ public interface IVisaApplicationsRepository : IGenericRepository GetByApplicantAndApplicationIdAsync(Guid applicantId, Guid applicationId, CancellationToken cancellationToken); + + /// Returns pending applications for approving authorities + Task> GetPendingApplicationsAsync(CancellationToken cancellationToken); } diff --git a/SchengenVisaApi/Domains/VisaApplicationDomain/VisaCategory.cs b/SchengenVisaApi/Domains/VisaApplicationDomain/VisaCategory.cs index c3690f6..cd41d76 100644 --- a/SchengenVisaApi/Domains/VisaApplicationDomain/VisaCategory.cs +++ b/SchengenVisaApi/Domains/VisaApplicationDomain/VisaCategory.cs @@ -5,4 +5,4 @@ public enum VisaCategory { Transit, ShortDated -} \ No newline at end of file +} diff --git a/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs b/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs index 2a40dd7..bfc6224 100644 --- a/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs +++ b/SchengenVisaApi/Infrastructure/Database/VisaApplications/Repositories/VisaApplicationsRepository.cs @@ -27,4 +27,10 @@ public sealed class VisaApplicationsRepository(IGenericReader reader, IGenericWr .SingleOrDefaultAsync(va => va.Id == applicationId && va.ApplicantId == applicantId, cancellationToken); return result ?? throw new ApplicationNotFoundByApplicantAndApplicationIdException(applicationId); } + + async Task> IVisaApplicationsRepository.GetPendingApplicationsAsync(CancellationToken cancellationToken) + { + var result = LoadDomain().Where(va => va.Status == ApplicationStatus.Pending); + return await result.ToListAsync(cancellationToken); + } } diff --git a/SchengenVisaApi/SchengenVisaApi/Common/ConfigureSwaggerOptions.cs b/SchengenVisaApi/SchengenVisaApi/Common/ConfigureSwaggerOptions.cs deleted file mode 100644 index 4c7da47..0000000 --- a/SchengenVisaApi/SchengenVisaApi/Common/ConfigureSwaggerOptions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace SchengenVisaApi.Common; - -/// Adds auth for swagger -public class ConfigureSwaggerOptions : IConfigureOptions -{ - void IConfigureOptions.Configure(SwaggerGenOptions options) - { - options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme - { - In = ParameterLocation.Header, - Description = "Provide a JWT-token.", - Name = "Authorization", - Type = SecuritySchemeType.Http, - BearerFormat = "JWT", - Scheme = "Bearer" - }); - - options.AddSecurityRequirement(new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "Bearer" - } - }, - Array.Empty() - } - }); - } -} \ No newline at end of file diff --git a/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs b/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs index 98d8baf..fc10d47 100644 --- a/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs +++ b/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs @@ -22,12 +22,11 @@ public class UsersController( IValidator registerApplicantRequestValidator, IValidator authDataValidator) : ControllerBase { - /// Adds applicant with user account to DB - [HttpPost] + /// Adds applicant with user account + [HttpPost("register")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [Route("register")] public async Task Register(RegisterApplicantRequest request, CancellationToken cancellationToken) { await registerApplicantRequestValidator.ValidateAndThrowAsync(request, cancellationToken); @@ -36,15 +35,14 @@ public class UsersController( return Ok(); } - /// Adds approving authority with user account to DB + /// Adds approving authority with user account /// Accessible only for admins - [HttpPost] + [HttpPost("authorities")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status409Conflict)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [Route("authorities")] [Authorize(policy: PolicyConstants.AdminPolicy)] public async Task RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken) { @@ -55,10 +53,9 @@ public class UsersController( } /// Returns JWT-token for authentication - [HttpGet] + [HttpGet("login")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - [Route("login")] public async Task Login(string email, string password, CancellationToken cancellationToken) { var result = await loginService.LoginAsync(email, password, cancellationToken); @@ -67,11 +64,10 @@ public class UsersController( /// Returns list of authority accounts /// Accessible only for admins - [HttpGet] + [HttpGet("authorities")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [Route("authorities")] [Authorize(policy: PolicyConstants.AdminPolicy)] public async Task GetAuthorityAccounts(CancellationToken cancellationToken) { @@ -81,13 +77,12 @@ public class UsersController( /// Changes authority's account authentication data /// Accessible only for admins - [HttpPut] + [HttpPut("authorities/{authorityAccountId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [Route("authorities/{authorityAccountId:guid}")] [Authorize(policy: PolicyConstants.AdminPolicy)] public async Task ChangeAuthorityAuthData(Guid authorityAccountId, AuthData authData, CancellationToken cancellationToken) { @@ -97,18 +92,17 @@ public class UsersController( return Ok(); } - /// Removes authority's account authentication data + /// Removes authority's account /// Accessible only for admins - [HttpDelete] + [HttpDelete("authorities/{authorityAccountId:guid}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [Route("authorities/{authorityAccountId:guid}")] [Authorize(policy: PolicyConstants.AdminPolicy)] public async Task RemoveAuthorityAccount(Guid authorityAccountId, CancellationToken cancellationToken) { await usersService.RemoveUserAccount(authorityAccountId, cancellationToken); return Ok(); } -} \ No newline at end of file +} diff --git a/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs b/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs index bbeb650..b415f16 100644 --- a/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs +++ b/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs @@ -8,42 +8,41 @@ using SchengenVisaApi.Common; namespace SchengenVisaApi.Controllers; -/// Controller for +/// Controller for visa applications [ApiController] [Route("visaApplications")] public class VisaApplicationController( IVisaApplicationRequestsHandler visaApplicationRequestsHandler, IValidator visaApplicationCreateRequestValidator) : ControllerBase { - /// Returns all applications from DB + /// Returns pending applications /// Accessible only for approving authorities - [HttpGet] + [HttpGet("pending")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [Authorize(policy: PolicyConstants.ApprovingAuthorityPolicy)] - public async Task Get(CancellationToken cancellationToken) + public async Task GetPending(CancellationToken cancellationToken) { - var result = await visaApplicationRequestsHandler.GetAllAsync(cancellationToken); + var result = await visaApplicationRequestsHandler.GetPendingAsync(cancellationToken); return Ok(result); } /// Returns all applications of one applicant /// Returns applications of authorized applicant - [HttpGet] + [HttpGet("ofApplicant")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] [Authorize(policy: PolicyConstants.ApplicantPolicy)] - [Route("OfApplicant")] public async Task GetForApplicant(CancellationToken cancellationToken) { var result = await visaApplicationRequestsHandler.GetForApplicantAsync(cancellationToken); return Ok(result); } - /// Adds new application to DB + /// Adds new application /// Adds application for authorized applicant [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] @@ -52,7 +51,7 @@ public class VisaApplicationController( [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [Authorize(policy: PolicyConstants.ApplicantPolicy)] - public async Task Create(VisaApplicationCreateRequest request, CancellationToken cancellationToken) + public async Task CreateApplication(VisaApplicationCreateRequest request, CancellationToken cancellationToken) { await visaApplicationCreateRequestValidator.ValidateAndThrowAsync(request, cancellationToken); @@ -62,28 +61,26 @@ public class VisaApplicationController( /// Sets application status to closed /// Accessible only for applicant - [HttpPatch] + [HttpPatch("{applicationId:guid}")] [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) { await visaApplicationRequestsHandler.HandleCloseRequestAsync(applicationId, cancellationToken); return Ok(); } - /// Allows approving authorities approve or reject applications + /// Approve or reject applications /// Accessible only for authorities - [HttpPatch] + [HttpPatch("approving/{applicationId:guid}")] [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); diff --git a/SchengenVisaApi/SchengenVisaApi/DependencyInjection.cs b/SchengenVisaApi/SchengenVisaApi/DependencyInjection.cs index 90693bb..8f1b336 100644 --- a/SchengenVisaApi/SchengenVisaApi/DependencyInjection.cs +++ b/SchengenVisaApi/SchengenVisaApi/DependencyInjection.cs @@ -1,14 +1,15 @@ using System.Reflection; using System.Security.Claims; using System.Text; +using System.Text.Json.Serialization; using ApplicationLayer; using Domains.Users; using Infrastructure; using Infrastructure.Auth; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; using SchengenVisaApi.Common; using SchengenVisaApi.ExceptionFilters; using Swashbuckle.AspNetCore.SwaggerGen; @@ -42,7 +43,8 @@ public static class DependencyInjection services.AddProblemDetails(); - services.AddControllers(opts => opts.Filters.Add()); + services.AddControllers(opts => opts.Filters.Add()) + .AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); } /// Adds authentication, authorization and token generator @@ -90,11 +92,38 @@ public static class DependencyInjection /// Add swagger private static void AddSwagger(this IServiceCollection services) { - services.AddTransient, ConfigureSwaggerOptions>(); services.AddSwaggerGen(options => { var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); + + options.CustomOperationIds(apiDescription => + apiDescription.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null); + + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Provide a JWT-token.", + Name = "Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = "Bearer" + }); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + Array.Empty() + } + }); }); } }