| @@ -11,6 +11,8 @@ | |||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
|  |  | ||||||
|     <ItemGroup> |     <ItemGroup> | ||||||
|  |       <PackageReference Include="FluentValidation" Version="11.9.2" /> | ||||||
|  |       <PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" /> | ||||||
|       <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0-preview.7.24405.7" /> |       <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0-preview.7.24405.7" /> | ||||||
|     </ItemGroup> |     </ItemGroup> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| using ApplicationLayer.Services.ApprovingAuthorities; | using System.Reflection; | ||||||
| using ApplicationLayer.Services.AuthServices.LoginService; | using ApplicationLayer.Services.AuthServices.LoginService; | ||||||
| using ApplicationLayer.Services.AuthServices.RegisterService; | using ApplicationLayer.Services.AuthServices.RegisterService; | ||||||
|  | using ApplicationLayer.Services.Users; | ||||||
| using ApplicationLayer.Services.VisaApplications.Handlers; | using ApplicationLayer.Services.VisaApplications.Handlers; | ||||||
|  | using FluentValidation; | ||||||
| using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||||
|  |  | ||||||
| namespace ApplicationLayer; | namespace ApplicationLayer; | ||||||
| @@ -12,6 +14,8 @@ public static class DependencyInjection | |||||||
|     /// Add services for Application layer |     /// Add services for Application layer | ||||||
|     public static IServiceCollection AddApplicationLayer(this IServiceCollection services, bool isDevelopment = false) |     public static IServiceCollection AddApplicationLayer(this IServiceCollection services, bool isDevelopment = false) | ||||||
|     { |     { | ||||||
|  |         services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); | ||||||
|  |  | ||||||
|         services.AddScoped<IVisaApplicationRequestsHandler, VisaApplicationRequestsHandler>(); |         services.AddScoped<IVisaApplicationRequestsHandler, VisaApplicationRequestsHandler>(); | ||||||
|  |  | ||||||
|         services.AddScoped<IRegisterService, RegisterService>(); |         services.AddScoped<IRegisterService, RegisterService>(); | ||||||
|   | |||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | namespace ApplicationLayer.InfrastructureServicesInterfaces | ||||||
|  | { | ||||||
|  |     public interface IUserIdProvider | ||||||
|  |     { | ||||||
|  |         /// Returns identifier of authenticated user who sent the request | ||||||
|  |         Guid GetUserId(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | using Domains.ApplicantDomain; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.Applicants.Models | ||||||
|  | { | ||||||
|  |     public class PlaceOfWorkModel | ||||||
|  |     { | ||||||
|  |         /// Name of hirer | ||||||
|  |         public string Name { get; set; } = null!; | ||||||
|  |  | ||||||
|  |         /// Address of hirer | ||||||
|  |         public Address Address { get; set; } = null!; | ||||||
|  |  | ||||||
|  |         /// Phone number of hirer | ||||||
|  |         public string PhoneNum { get; set; } = null!; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -11,4 +11,7 @@ public interface IApplicantsRepository : IGenericRepository<Applicant> | |||||||
|  |  | ||||||
|     /// Get identifier of applicant by user identifier |     /// Get identifier of applicant by user identifier | ||||||
|     Task<Guid> GetApplicantIdByUserId(Guid userId, CancellationToken cancellationToken); |     Task<Guid> GetApplicantIdByUserId(Guid userId, CancellationToken cancellationToken); | ||||||
|  |  | ||||||
|  |     /// Returns value of NonResident property of applicant | ||||||
|  |     Task<bool> IsApplicantNonResidentByUserId(Guid userId, CancellationToken cancellationToken); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,4 @@ | |||||||
|  | namespace ApplicationLayer.Services.AuthServices.Common | ||||||
|  | { | ||||||
|  |     public record AuthData(string Email, string Password); | ||||||
|  | } | ||||||
| @@ -1,23 +1,22 @@ | |||||||
| using ApplicationLayer.Services.AuthServices.LoginService.Exceptions; | using ApplicationLayer.Services.AuthServices.LoginService.Exceptions; | ||||||
| using ApplicationLayer.Services.AuthServices.NeededServices; | using ApplicationLayer.Services.AuthServices.NeededServices; | ||||||
| using ApplicationLayer.Services.AuthServices.Requests; |  | ||||||
| using Domains.Users; | using Domains.Users; | ||||||
|  |  | ||||||
| namespace ApplicationLayer.Services.AuthServices.LoginService | namespace ApplicationLayer.Services.AuthServices.LoginService | ||||||
| { | { | ||||||
|     public class DevelopmentLoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService |     public class DevelopmentLoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService | ||||||
|     { |     { | ||||||
|         async Task<string> ILoginService.LoginAsync(UserLoginRequest request, CancellationToken cancellationToken) |         async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             if (request is { Email: "admin@mail.ru", Password: "admin" }) |             if (email == "admin@mail.ru" && password == "admin") | ||||||
|             { |             { | ||||||
|                 var admin = new User { Role = Role.Admin }; |                 var admin = new User { Role = Role.Admin }; | ||||||
|  |  | ||||||
|                 return tokenGenerator.CreateToken(admin); |                 return tokenGenerator.CreateToken(admin); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             var user = await users.FindByEmailAsync(request.Email, cancellationToken); |             var user = await users.FindByEmailAsync(email, cancellationToken); | ||||||
|             if (user is null || user.Password != request.Password) |             if (user is null || user.Password != password) | ||||||
|             { |             { | ||||||
|                 throw new IncorrectLoginDataException(); |                 throw new IncorrectLoginDataException(); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -1,12 +1,10 @@ | |||||||
| using ApplicationLayer.Services.AuthServices.Requests; | namespace ApplicationLayer.Services.AuthServices.LoginService | ||||||
|  |  | ||||||
| namespace ApplicationLayer.Services.AuthServices.LoginService |  | ||||||
| { | { | ||||||
|     /// Handles <see cref="UserLoginRequest"/> |     /// Handles login requests | ||||||
|     public interface ILoginService |     public interface ILoginService | ||||||
|     { |     { | ||||||
|         /// Handle <see cref="UserLoginRequest"/> |         /// Handle login request | ||||||
|         /// <returns>JWT-token</returns> |         /// <returns>JWT-token</returns> | ||||||
|         Task<string> LoginAsync(UserLoginRequest request, CancellationToken cancellationToken); |         Task<string> LoginAsync(string email, string password, CancellationToken cancellationToken); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,16 +1,15 @@ | |||||||
| using ApplicationLayer.Services.AuthServices.LoginService.Exceptions; | using ApplicationLayer.Services.AuthServices.LoginService.Exceptions; | ||||||
| using ApplicationLayer.Services.AuthServices.NeededServices; | using ApplicationLayer.Services.AuthServices.NeededServices; | ||||||
| using ApplicationLayer.Services.AuthServices.Requests; |  | ||||||
|  |  | ||||||
| namespace ApplicationLayer.Services.AuthServices.LoginService | namespace ApplicationLayer.Services.AuthServices.LoginService | ||||||
| { | { | ||||||
|     /// <inheritdoc cref="ILoginService"/> |     /// <inheritdoc cref="ILoginService"/> | ||||||
|     public class LoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService |     public class LoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService | ||||||
|     { |     { | ||||||
|         async Task<string> ILoginService.LoginAsync(UserLoginRequest request, CancellationToken cancellationToken) |         async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var user = await users.FindByEmailAsync(request.Email, cancellationToken); |             var user = await users.FindByEmailAsync(email, cancellationToken); | ||||||
|             if (user is null || user.Password != request.Password) |             if (user is null || user.Password != password) | ||||||
|             { |             { | ||||||
|                 throw new IncorrectLoginDataException(); |                 throw new IncorrectLoginDataException(); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -1,7 +0,0 @@ | |||||||
| using ApplicationLayer.GeneralExceptions; |  | ||||||
| using ApplicationLayer.Services.AuthServices.Requests; |  | ||||||
|  |  | ||||||
| namespace ApplicationLayer.Services.AuthServices.RegisterService.Exceptions |  | ||||||
| { |  | ||||||
|     public class UserAlreadyExistsException(RegisterRequest request) : AlreadyExistsException($"User with email '{request.Email}' already exists"); |  | ||||||
| } |  | ||||||
| @@ -6,7 +6,7 @@ namespace ApplicationLayer.Services.AuthServices.RegisterService | |||||||
|     public interface IRegisterService |     public interface IRegisterService | ||||||
|     { |     { | ||||||
|         /// Handle <see cref="RegisterApplicantRequest"/> |         /// Handle <see cref="RegisterApplicantRequest"/> | ||||||
|         Task Register(RegisterApplicantRequest request, CancellationToken cancellationToken); |         Task RegisterApplicant(RegisterApplicantRequest request, CancellationToken cancellationToken); | ||||||
|  |  | ||||||
|         /// Handles <see cref="RegisterRequest"/> and adds approving authority account |         /// Handles <see cref="RegisterRequest"/> and adds approving authority account | ||||||
|         Task RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken); |         Task RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken); | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| using ApplicationLayer.InfrastructureServicesInterfaces; | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
| using ApplicationLayer.Services.Applicants.NeededServices; | using ApplicationLayer.Services.Applicants.NeededServices; | ||||||
| using ApplicationLayer.Services.AuthServices.NeededServices; | using ApplicationLayer.Services.AuthServices.NeededServices; | ||||||
| using ApplicationLayer.Services.AuthServices.RegisterService.Exceptions; |  | ||||||
| using ApplicationLayer.Services.AuthServices.Requests; | using ApplicationLayer.Services.AuthServices.Requests; | ||||||
|  | using AutoMapper; | ||||||
| using Domains.ApplicantDomain; | using Domains.ApplicantDomain; | ||||||
| using Domains.Users; | using Domains.Users; | ||||||
|  |  | ||||||
| @@ -12,37 +12,16 @@ namespace ApplicationLayer.Services.AuthServices.RegisterService | |||||||
|     public class RegisterService( |     public class RegisterService( | ||||||
|         IUsersRepository users, |         IUsersRepository users, | ||||||
|         IApplicantsRepository applicants, |         IApplicantsRepository applicants, | ||||||
|         IUnitOfWork unitOfWork) : IRegisterService |         IUnitOfWork unitOfWork, | ||||||
|  |         IMapper mapper) : IRegisterService | ||||||
|     { |     { | ||||||
|         async Task IRegisterService.Register(RegisterApplicantRequest request, CancellationToken cancellationToken) |         async Task IRegisterService.RegisterApplicant(RegisterApplicantRequest request, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             //todo move to validation layer |             var user = mapper.Map<User>(request.AuthData); | ||||||
|             if (await users.FindByEmailAsync(request.Email, cancellationToken) is not null) |             user.Role = Role.Applicant; | ||||||
|             { |  | ||||||
|                 throw new UserAlreadyExistsException(request); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             //TODO mapper |             var applicant = mapper.Map<Applicant>(request); | ||||||
|             var user = new User { Email = request.Email, Password = request.Password, Role = Role.Applicant }; |             applicant.UserId = user.Id; | ||||||
|  |  | ||||||
|             var applicant = new Applicant |  | ||||||
|             { |  | ||||||
|                 Citizenship = request.Citizenship, |  | ||||||
|                 CitizenshipByBirth = request.CitizenshipByBirth, |  | ||||||
|                 Gender = request.Gender, |  | ||||||
|                 Name = request.ApplicantName, |  | ||||||
|                 Passport = request.Passport, |  | ||||||
|                 BirthDate = request.BirthDate, |  | ||||||
|                 FatherName = request.FatherName, |  | ||||||
|                 JobTitle = request.JobTitle, |  | ||||||
|                 MaritalStatus = request.MaritalStatus, |  | ||||||
|                 MotherName = request.MotherName, |  | ||||||
|                 UserId = user.Id, |  | ||||||
|                 CityOfBirth = request.CityOfBirth, |  | ||||||
|                 CountryOfBirth = request.CountryOfBirth, |  | ||||||
|                 IsNonResident = request.IsNonResident, |  | ||||||
|                 PlaceOfWork = request.PlaceOfWork |  | ||||||
|             }; |  | ||||||
|  |  | ||||||
|             await users.AddAsync(user, cancellationToken); |             await users.AddAsync(user, cancellationToken); | ||||||
|             await applicants.AddAsync(applicant, cancellationToken); |             await applicants.AddAsync(applicant, cancellationToken); | ||||||
| @@ -52,14 +31,8 @@ namespace ApplicationLayer.Services.AuthServices.RegisterService | |||||||
|  |  | ||||||
|         async Task IRegisterService.RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken) |         async Task IRegisterService.RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             //todo move to validation layer |             var user = mapper.Map<User>(request.AuthData); | ||||||
|             if (await users.FindByEmailAsync(request.Email, cancellationToken) is not null) |             user.Role = Role.ApprovingAuthority; | ||||||
|             { |  | ||||||
|                 throw new UserAlreadyExistsException(request); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             //TODO mapper |  | ||||||
|             var user = new User { Email = request.Email, Password = request.Password, Role = Role.ApprovingAuthority }; |  | ||||||
|  |  | ||||||
|             await users.AddAsync(user, cancellationToken); |             await users.AddAsync(user, cancellationToken); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,11 @@ | |||||||
| using Domains.ApplicantDomain; | using ApplicationLayer.Services.Applicants.Models; | ||||||
|  | using ApplicationLayer.Services.AuthServices.Common; | ||||||
|  | using Domains.ApplicantDomain; | ||||||
|  |  | ||||||
| namespace ApplicationLayer.Services.AuthServices.Requests | namespace ApplicationLayer.Services.AuthServices.Requests | ||||||
| { | { | ||||||
|     public record RegisterApplicantRequest( |     public record RegisterApplicantRequest( | ||||||
|         string Email, |         AuthData AuthData, | ||||||
|         string Password, |  | ||||||
|         Name ApplicantName, |         Name ApplicantName, | ||||||
|         Passport Passport, |         Passport Passport, | ||||||
|         DateTime BirthDate, |         DateTime BirthDate, | ||||||
| @@ -17,6 +18,6 @@ namespace ApplicationLayer.Services.AuthServices.Requests | |||||||
|         Name FatherName, |         Name FatherName, | ||||||
|         Name MotherName, |         Name MotherName, | ||||||
|         string JobTitle, |         string JobTitle, | ||||||
|         PlaceOfWork PlaceOfWork, |         PlaceOfWorkModel PlaceOfWork, | ||||||
|         bool IsNonResident) : RegisterRequest(Email, Password); |         bool IsNonResident) : RegisterRequest(AuthData); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| namespace ApplicationLayer.Services.AuthServices.Requests | using ApplicationLayer.Services.AuthServices.Common; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.AuthServices.Requests | ||||||
| { | { | ||||||
|     public record RegisterRequest(string Email, string Password); |     public record RegisterRequest(AuthData AuthData); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +0,0 @@ | |||||||
| namespace ApplicationLayer.Services.AuthServices.Requests |  | ||||||
| { |  | ||||||
|     public record UserLoginRequest(string Email, string Password); |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,32 @@ | |||||||
|  | using ApplicationLayer.Services.AuthServices.Common; | ||||||
|  | using ApplicationLayer.Services.AuthServices.NeededServices; | ||||||
|  | using Domains; | ||||||
|  | using FluentValidation; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.AuthServices.Requests.Validation | ||||||
|  | { | ||||||
|  |     public class AuthDataValidator : AbstractValidator<AuthData> | ||||||
|  |     { | ||||||
|  |         public AuthDataValidator(IUsersRepository users) | ||||||
|  |         { | ||||||
|  |             RuleFor(d => d.Email) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Email can not be empty") | ||||||
|  |                 .EmailAddress() | ||||||
|  |                 .WithMessage("Email must be valid") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.EmailLength) | ||||||
|  |                 .WithMessage($"Email length must be less than {ConfigurationConstraints.EmailLength}") | ||||||
|  |                 .MustAsync(async (email, ct) => | ||||||
|  |                 { | ||||||
|  |                     return await users.FindByEmailAsync(email, ct) is null; | ||||||
|  |                 }) | ||||||
|  |                 .WithMessage("Email already exists"); | ||||||
|  |  | ||||||
|  |             RuleFor(d => d.Password) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Password can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.PasswordLength) | ||||||
|  |                 .WithMessage($"Password length must be less than {ConfigurationConstraints.PasswordLength}"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | using Domains; | ||||||
|  | using Domains.ApplicantDomain; | ||||||
|  | using FluentValidation; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.AuthServices.Requests.Validation | ||||||
|  | { | ||||||
|  |     public class NameValidator : AbstractValidator<Name> | ||||||
|  |     { | ||||||
|  |         public NameValidator() | ||||||
|  |         { | ||||||
|  |             RuleFor(m => m.FirstName) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("First Name can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.NameLength) | ||||||
|  |                 .WithMessage($"First Name length must be less than {ConfigurationConstraints.NameLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(m => m.Surname) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Surname can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.NameLength) | ||||||
|  |                 .WithMessage($"Surname length must be less than {ConfigurationConstraints.NameLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(m => m.Patronymic) | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.NameLength) | ||||||
|  |                 .WithMessage($"Patronymic length must be less than {ConfigurationConstraints.NameLength}"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,37 @@ | |||||||
|  | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
|  | using Domains; | ||||||
|  | using Domains.ApplicantDomain; | ||||||
|  | using FluentValidation; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.AuthServices.Requests.Validation | ||||||
|  | { | ||||||
|  |     public class PassportValidator : AbstractValidator<Passport> | ||||||
|  |     { | ||||||
|  |         public PassportValidator(IDateTimeProvider dateTimeProvider) | ||||||
|  |         { | ||||||
|  |             RuleFor(r => r.Issuer) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Passport issuer can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.IssuerNameLength) | ||||||
|  |                 .WithMessage($"Passport issuer length must be less than {ConfigurationConstraints.IssuerNameLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.Number) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Passport number can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.PassportNumberLength) | ||||||
|  |                 .WithMessage($"Passport number length must be less than {ConfigurationConstraints.PassportNumberLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.ExpirationDate) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Passport expiration date can not be empty") | ||||||
|  |                 .GreaterThan(dateTimeProvider.Now()) | ||||||
|  |                 .WithMessage("Can not approve visa for applicants with expired passport"); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.IssueDate) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Passport issue date can not be empty") | ||||||
|  |                 .LessThanOrEqualTo(dateTimeProvider.Now()) | ||||||
|  |                 .WithMessage("Passport issue date must be in past"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | using ApplicationLayer.Services.Applicants.Models; | ||||||
|  | using Domains; | ||||||
|  | using FluentValidation; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.AuthServices.Requests.Validation | ||||||
|  | { | ||||||
|  |     public class PlaceOfWorkModelValidator : AbstractValidator<PlaceOfWorkModel> | ||||||
|  |     { | ||||||
|  |         public PlaceOfWorkModelValidator() | ||||||
|  |         { | ||||||
|  |             RuleFor(p => p.Name) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Place of work name can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.PlaceOfWorkNameLength) | ||||||
|  |                 .WithMessage($"Place of work name length must be less than {ConfigurationConstraints.PlaceOfWorkNameLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(p => p.PhoneNum) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Place of work phone number can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.PhoneNumberLength) | ||||||
|  |                 .WithMessage($"Phone number length must be in range from {ConfigurationConstraints.PhoneNumberMinLength} to {ConfigurationConstraints.PhoneNumberLength}") | ||||||
|  |                 .MinimumLength(ConfigurationConstraints.PhoneNumberMinLength) | ||||||
|  |                 .WithMessage($"Phone number length must be in range from {ConfigurationConstraints.PhoneNumberMinLength} to {ConfigurationConstraints.PhoneNumberLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(p => p.Address.Country) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Country name of place of work can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.CountryNameLength) | ||||||
|  |                 .WithMessage($"Country name of place of work length must be less than {ConfigurationConstraints.CountryNameLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(p => p.Address.City) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("City name of place of work can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.CityNameLength) | ||||||
|  |                 .WithMessage($"City name of place of work length must be less than {ConfigurationConstraints.CityNameLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(p => p.Address.Street) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Street name of place of work can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.StreetNameLength) | ||||||
|  |                 .WithMessage($"Street name of place of work length must be less than {ConfigurationConstraints.StreetNameLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(p => p.Address.Building) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Building of place of work can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.CountryNameLength) | ||||||
|  |                 .WithMessage($"Building of place of work length must be less than {ConfigurationConstraints.BuildingNumberLength}"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,78 @@ | |||||||
|  | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
|  | using ApplicationLayer.Services.Applicants.Models; | ||||||
|  | using ApplicationLayer.Services.AuthServices.Common; | ||||||
|  | using Domains; | ||||||
|  | using Domains.ApplicantDomain; | ||||||
|  | using FluentValidation; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.AuthServices.Requests.Validation | ||||||
|  | { | ||||||
|  |     public class RegisterApplicantRequestValidator : AbstractValidator<RegisterApplicantRequest> | ||||||
|  |     { | ||||||
|  |         public RegisterApplicantRequestValidator( | ||||||
|  |             IDateTimeProvider dateTimeProvider, | ||||||
|  |             IValidator<Name> nameValidator, | ||||||
|  |             IValidator<AuthData> authDataValidator, | ||||||
|  |             IValidator<Passport> passportValidator, | ||||||
|  |             IValidator<PlaceOfWorkModel> placeOfWorkModelValidator) | ||||||
|  |         { | ||||||
|  |             RuleFor(r => r.AuthData) | ||||||
|  |                 .SetValidator(authDataValidator); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.ApplicantName) | ||||||
|  |                 .SetValidator(nameValidator); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.FatherName) | ||||||
|  |                 .SetValidator(nameValidator); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.MotherName) | ||||||
|  |                 .SetValidator(nameValidator); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.Passport) | ||||||
|  |                 .SetValidator(passportValidator); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.BirthDate) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Birth date can not be empty") | ||||||
|  |                 .LessThanOrEqualTo(dateTimeProvider.Now().AddYears(-ConfigurationConstraints.ApplicantMinAge)) | ||||||
|  |                 .WithMessage($"Applicant must be older than {ConfigurationConstraints.ApplicantMinAge}"); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.CountryOfBirth) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Country of birth can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.CountryNameLength) | ||||||
|  |                 .WithMessage($"Country of birth name length must be less than {ConfigurationConstraints.CountryNameLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.CityOfBirth) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("City of birth can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.CityNameLength) | ||||||
|  |                 .WithMessage($"City of birth name length must be less than {ConfigurationConstraints.CityNameLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.Citizenship) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Citizenship can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.CitizenshipLength) | ||||||
|  |                 .WithMessage($"Citizenship length must be less than {ConfigurationConstraints.CitizenshipLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.CitizenshipByBirth) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Citizenship by birth can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.CitizenshipLength) | ||||||
|  |                 .WithMessage($"Citizenship by birth length must be less than {ConfigurationConstraints.CitizenshipLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.Gender).IsInEnum(); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.MaritalStatus).IsInEnum(); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.JobTitle) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Title of job can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.JobTitleLength) | ||||||
|  |                 .WithMessage($"Title of job length must be less than {ConfigurationConstraints.JobTitleLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.PlaceOfWork) | ||||||
|  |                 .SetValidator(placeOfWorkModelValidator); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| using ApplicationLayer.GeneralExceptions; |  | ||||||
|  |  | ||||||
| namespace ApplicationLayer.Services.GeneralExceptions |  | ||||||
| { |  | ||||||
|     /// Exception to throw when can't complete some action on entity(delete or something) because it's needed for other entities |  | ||||||
|     public class EntityUsedInDatabaseException(string message) : ApiException(message); |  | ||||||
| } |  | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| using ApplicationLayer.Services.AuthServices.Requests; | using ApplicationLayer.Services.Users.Requests; | ||||||
| using Domains.Users; | using Domains.Users; | ||||||
| 
 | 
 | ||||||
| namespace ApplicationLayer.Services.ApprovingAuthorities | namespace ApplicationLayer.Services.Users | ||||||
| { | { | ||||||
|     /// user accounts service |     /// user accounts service | ||||||
|     public interface IUsersService |     public interface IUsersService | ||||||
| @@ -11,10 +11,9 @@ namespace ApplicationLayer.Services.ApprovingAuthorities | |||||||
|         Task<List<User>> GetAuthoritiesAccountsAsync(CancellationToken cancellationToken); |         Task<List<User>> GetAuthoritiesAccountsAsync(CancellationToken cancellationToken); | ||||||
| 
 | 
 | ||||||
|         /// Changes authentication data for an account |         /// Changes authentication data for an account | ||||||
|         /// <param name="userId">identifier of account</param> |         /// <param name="request"> Request object with identifier of user and new authentication data</param> | ||||||
|         /// <param name="data">request data with new email and password</param> |  | ||||||
|         /// <param name="cancellationToken">Cancellation token</param> |         /// <param name="cancellationToken">Cancellation token</param> | ||||||
|         Task ChangeAccountAuthDataAsync(Guid userId, RegisterRequest data, CancellationToken cancellationToken); |         Task ChangeAccountAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken); | ||||||
| 
 | 
 | ||||||
|         /// Removes user account |         /// Removes user account | ||||||
|         /// <param name="userId">Identifier of account</param> |         /// <param name="userId">Identifier of account</param> | ||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | using ApplicationLayer.Services.AuthServices.Common; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.Users.Requests | ||||||
|  | { | ||||||
|  |     public record ChangeUserAuthDataRequest(Guid UserId, AuthData NewAuthData); | ||||||
|  | } | ||||||
| @@ -1,9 +1,9 @@ | |||||||
| using ApplicationLayer.InfrastructureServicesInterfaces; | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
| using ApplicationLayer.Services.AuthServices.NeededServices; | using ApplicationLayer.Services.AuthServices.NeededServices; | ||||||
| using ApplicationLayer.Services.AuthServices.Requests; | using ApplicationLayer.Services.Users.Requests; | ||||||
| using Domains.Users; | using Domains.Users; | ||||||
| 
 | 
 | ||||||
| namespace ApplicationLayer.Services.ApprovingAuthorities | namespace ApplicationLayer.Services.Users | ||||||
| { | { | ||||||
|     public class UsersService(IUsersRepository users, IUnitOfWork unitOfWork) : IUsersService |     public class UsersService(IUsersRepository users, IUnitOfWork unitOfWork) : IUsersService | ||||||
|     { |     { | ||||||
| @@ -12,12 +12,12 @@ namespace ApplicationLayer.Services.ApprovingAuthorities | |||||||
|             return await users.GetAllOfRoleAsync(Role.ApprovingAuthority, cancellationToken); |             return await users.GetAllOfRoleAsync(Role.ApprovingAuthority, cancellationToken); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         async Task IUsersService.ChangeAccountAuthDataAsync(Guid userId, RegisterRequest data, CancellationToken cancellationToken) |         async Task IUsersService.ChangeAccountAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var user = await users.GetByIdAsync(userId, cancellationToken); |             var user = await users.GetByIdAsync(request.UserId, cancellationToken); | ||||||
| 
 | 
 | ||||||
|             user.Email = data.Email; |             user.Email = request.NewAuthData.Email; | ||||||
|             user.Password = data.Password; |             user.Password = request.NewAuthData.Password; | ||||||
|             await users.UpdateAsync(user, cancellationToken); |             await users.UpdateAsync(user, cancellationToken); | ||||||
| 
 | 
 | ||||||
|             await unitOfWork.SaveAsync(cancellationToken); |             await unitOfWork.SaveAsync(cancellationToken); | ||||||
| @@ -5,6 +5,7 @@ using ApplicationLayer.Services.VisaApplications.Exceptions; | |||||||
| using ApplicationLayer.Services.VisaApplications.Models; | using ApplicationLayer.Services.VisaApplications.Models; | ||||||
| using ApplicationLayer.Services.VisaApplications.NeededServices; | using ApplicationLayer.Services.VisaApplications.NeededServices; | ||||||
| using ApplicationLayer.Services.VisaApplications.Requests; | using ApplicationLayer.Services.VisaApplications.Requests; | ||||||
|  | using AutoMapper; | ||||||
| using Domains.VisaApplicationDomain; | using Domains.VisaApplicationDomain; | ||||||
|  |  | ||||||
| namespace ApplicationLayer.Services.VisaApplications.Handlers; | namespace ApplicationLayer.Services.VisaApplications.Handlers; | ||||||
| @@ -13,13 +14,14 @@ namespace ApplicationLayer.Services.VisaApplications.Handlers; | |||||||
| public class VisaApplicationRequestsHandler( | public class VisaApplicationRequestsHandler( | ||||||
|     IVisaApplicationsRepository applications, |     IVisaApplicationsRepository applications, | ||||||
|     IApplicantsRepository applicants, |     IApplicantsRepository applicants, | ||||||
|     IUnitOfWork unitOfWork) : IVisaApplicationRequestsHandler |     IUnitOfWork unitOfWork, | ||||||
|  |     IMapper mapper, | ||||||
|  |     IDateTimeProvider dateTimeProvider) : IVisaApplicationRequestsHandler | ||||||
| { | { | ||||||
|     async Task<List<VisaApplicationModelForAuthority>> IVisaApplicationRequestsHandler.GetAllAsync(CancellationToken cancellationToken) |     async Task<List<VisaApplicationModelForAuthority>> IVisaApplicationRequestsHandler.GetAllAsync(CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var applicationsList = await applications.GetAllAsync(cancellationToken); |         var applicationsList = await applications.GetAllAsync(cancellationToken); | ||||||
|  |  | ||||||
|         //todo mapper |  | ||||||
|         var applicationModels = applicationsList |         var applicationModels = applicationsList | ||||||
|             .Select(a => MapVisaApplicationToModelForAuthorities(a, cancellationToken).Result) |             .Select(a => MapVisaApplicationToModelForAuthorities(a, cancellationToken).Result) | ||||||
|             .ToList(); |             .ToList(); | ||||||
| @@ -30,85 +32,28 @@ public class VisaApplicationRequestsHandler( | |||||||
|         CancellationToken cancellationToken) |         CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var applicant = await applicants.GetByIdAsync(visaApplication.ApplicantId, cancellationToken); |         var applicant = await applicants.GetByIdAsync(visaApplication.ApplicantId, cancellationToken); | ||||||
|         var applicantModel = new ApplicantModel |         var applicantModel = mapper.Map<ApplicantModel>(applicant); | ||||||
|         { |  | ||||||
|             Citizenship = applicant.Citizenship, |         var model = mapper.Map<VisaApplicationModelForAuthority>(visaApplication); | ||||||
|             Gender = applicant.Gender, |         model.Applicant = applicantModel; | ||||||
|             Name = applicant.Name, |  | ||||||
|             Passport = applicant.Passport, |         return model; | ||||||
|             BirthDate = applicant.BirthDate, |  | ||||||
|             FatherName = applicant.FatherName, |  | ||||||
|             JobTitle = applicant.JobTitle, |  | ||||||
|             MaritalStatus = applicant.MaritalStatus, |  | ||||||
|             MotherName = applicant.MotherName, |  | ||||||
|             CitizenshipByBirth = applicant.CitizenshipByBirth, |  | ||||||
|             CityOfBirth = applicant.CityOfBirth, |  | ||||||
|             CountryOfBirth = applicant.CountryOfBirth, |  | ||||||
|             IsNonResident = applicant.IsNonResident, |  | ||||||
|             PlaceOfWork = applicant.PlaceOfWork, |  | ||||||
|         }; |  | ||||||
|         return new VisaApplicationModelForAuthority |  | ||||||
|         { |  | ||||||
|             PastVisits = visaApplication.PastVisits, |  | ||||||
|             ReentryPermit = visaApplication.ReentryPermit, |  | ||||||
|             VisaCategory = visaApplication.VisaCategory, |  | ||||||
|             PermissionToDestCountry = visaApplication.PermissionToDestCountry, |  | ||||||
|             DestinationCountry = visaApplication.DestinationCountry, |  | ||||||
|             PastVisas = visaApplication.PastVisas, |  | ||||||
|             RequestDate = visaApplication.RequestDate, |  | ||||||
|             ValidDaysRequested = visaApplication.ValidDaysRequested, |  | ||||||
|             RequestedNumberOfEntries = visaApplication.RequestedNumberOfEntries, |  | ||||||
|             ForGroup = visaApplication.ForGroup, |  | ||||||
|             Applicant = applicantModel, |  | ||||||
|             Id = visaApplication.Id, |  | ||||||
|             Status = visaApplication.Status |  | ||||||
|         }; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async Task<List<VisaApplicationModelForApplicant>> GetForApplicantAsync(Guid userId, CancellationToken cancellationToken) |     public async Task<List<VisaApplicationModelForApplicant>> GetForApplicantAsync(Guid userId, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         //todo mapper |  | ||||||
|         var applicantId = await applicants.GetApplicantIdByUserId(userId, cancellationToken); |         var applicantId = await applicants.GetApplicantIdByUserId(userId, cancellationToken); | ||||||
|         var visaApplications = await applications.GetOfApplicantAsync(applicantId, cancellationToken); |         var visaApplications = await applications.GetOfApplicantAsync(applicantId, cancellationToken); | ||||||
|         return visaApplications.Select(va => new VisaApplicationModelForApplicant |         return mapper.Map<List<VisaApplicationModelForApplicant>>(visaApplications); | ||||||
|             { |  | ||||||
|                 DestinationCountry = va.DestinationCountry, |  | ||||||
|                 ValidDaysRequested = va.ValidDaysRequested, |  | ||||||
|                 ReentryPermit = va.ReentryPermit, |  | ||||||
|                 VisaCategory = va.VisaCategory, |  | ||||||
|                 RequestedNumberOfEntries = va.RequestedNumberOfEntries, |  | ||||||
|                 PermissionToDestCountry = va.PermissionToDestCountry, |  | ||||||
|                 ForGroup = va.ForGroup, |  | ||||||
|                 PastVisas = va.PastVisas, |  | ||||||
|                 RequestDate = va.RequestDate, |  | ||||||
|                 PastVisits = va.PastVisits, |  | ||||||
|                 Id = va.Id, |  | ||||||
|                 Status = va.Status |  | ||||||
|             }) |  | ||||||
|             .ToList(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async Task HandleCreateRequestAsync(Guid userId, VisaApplicationCreateRequest request, CancellationToken cancellationToken) |     public async Task HandleCreateRequestAsync(Guid userId, VisaApplicationCreateRequest request, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         //TODO mapper |  | ||||||
|  |  | ||||||
|         var applicant = await applicants.FindByUserIdAsync(userId, cancellationToken); |         var applicant = await applicants.FindByUserIdAsync(userId, cancellationToken); | ||||||
|  |  | ||||||
|         var visaApplication = new VisaApplication |         var visaApplication = mapper.Map<VisaApplication>(request); | ||||||
|         { |         visaApplication.RequestDate = dateTimeProvider.Now(); | ||||||
|             ApplicantId = applicant.Id, |         visaApplication.ApplicantId = applicant.Id; | ||||||
|             RequestedNumberOfEntries = request.RequestedNumberOfEntries, |  | ||||||
|             ValidDaysRequested = request.ValidDaysRequested, |  | ||||||
|             ReentryPermit = request.ReentryPermit, |  | ||||||
|             VisaCategory = request.VisaCategory, |  | ||||||
|             PermissionToDestCountry = request.PermissionToDestCountry, |  | ||||||
|             DestinationCountry = request.DestinationCountry, |  | ||||||
|             PastVisas = request.PastVisas.ToList(), |  | ||||||
|             PastVisits = request.PastVisits.ToList(), |  | ||||||
|             ForGroup = request.IsForGroup, |  | ||||||
|             RequestDate = DateTime.Today, |  | ||||||
|             Status = ApplicationStatus.Pending |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         await applications.AddAsync(visaApplication, cancellationToken); |         await applications.AddAsync(visaApplication, cancellationToken); | ||||||
|  |  | ||||||
| @@ -134,11 +79,9 @@ public class VisaApplicationRequestsHandler( | |||||||
|         var application = await applications.GetByIdAsync(applicationId, cancellationToken); |         var application = await applications.GetByIdAsync(applicationId, cancellationToken); | ||||||
|         if (application.Status != ApplicationStatus.Pending) |         if (application.Status != ApplicationStatus.Pending) | ||||||
|         { |         { | ||||||
|             //todo refactor exceptions |  | ||||||
|             throw new ApplicationAlreadyProcessedException(); |             throw new ApplicationAlreadyProcessedException(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         //todo mapper |  | ||||||
|         ApplicationStatus statusToSet = status switch |         ApplicationStatus statusToSet = status switch | ||||||
|         { |         { | ||||||
|             AuthorityRequestStatuses.Approved => ApplicationStatus.Approved, |             AuthorityRequestStatuses.Approved => ApplicationStatus.Approved, | ||||||
|   | |||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
|  | using Domains.VisaApplicationDomain; | ||||||
|  | using FluentValidation; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.VisaApplications.Requests.Validation | ||||||
|  | { | ||||||
|  |     public class PastVisaValidator : AbstractValidator<PastVisa> | ||||||
|  |     { | ||||||
|  |         public PastVisaValidator(IDateTimeProvider dateTimeProvider) | ||||||
|  |         { | ||||||
|  |             RuleFor(v => v.ExpirationDate) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Expiration date of past visa can not be empty") | ||||||
|  |                 .GreaterThan(v => v.IssueDate) | ||||||
|  |                 .WithMessage("Past visa expiration date can not be earlier than issue date"); | ||||||
|  |  | ||||||
|  |             RuleFor(v => v.IssueDate) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Issue date of past visa can not be empty") | ||||||
|  |                 .LessThan(dateTimeProvider.Now()) | ||||||
|  |                 .WithMessage("Issue date of past visa must be in past"); | ||||||
|  |  | ||||||
|  |             RuleFor(v => v.Name) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Name of past visa can not be empty"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,31 @@ | |||||||
|  | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
|  | using Domains; | ||||||
|  | using Domains.VisaApplicationDomain; | ||||||
|  | using FluentValidation; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.VisaApplications.Requests.Validation | ||||||
|  | { | ||||||
|  |     public class PastVisitValidator : AbstractValidator<PastVisit> | ||||||
|  |     { | ||||||
|  |         public PastVisitValidator(IDateTimeProvider dateTimeProvider) | ||||||
|  |         { | ||||||
|  |             RuleFor(v => v.StartDate) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Start date of past visit can not be empty") | ||||||
|  |                 .LessThan(v => v.EndDate) | ||||||
|  |                 .WithMessage("Start date of past visit must be earlier than end date") | ||||||
|  |                 .LessThan(dateTimeProvider.Now()) | ||||||
|  |                 .WithMessage("Start date of past visit must be in past"); | ||||||
|  |  | ||||||
|  |             RuleFor(v => v.EndDate) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("End date of past visit can not be empty"); | ||||||
|  |  | ||||||
|  |             RuleFor(v => v.DestinationCountry) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Destination Country of past visit can not be null") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.CountryNameLength) | ||||||
|  |                 .WithMessage($"Destination Country of past visit length must be less than {ConfigurationConstraints.CountryNameLength}"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,25 @@ | |||||||
|  | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
|  | using Domains; | ||||||
|  | using Domains.VisaApplicationDomain; | ||||||
|  | using FluentValidation; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.VisaApplications.Requests.Validation | ||||||
|  | { | ||||||
|  |     public class PermissionToDestCountryValidator : AbstractValidator<PermissionToDestCountry?> | ||||||
|  |     { | ||||||
|  |         public PermissionToDestCountryValidator(IDateTimeProvider dateTimeProvider) | ||||||
|  |         { | ||||||
|  |             RuleFor(p => p!.ExpirationDate) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Expiration date of permission to destination Country can not be empty") | ||||||
|  |                 .GreaterThan(dateTimeProvider.Now()) | ||||||
|  |                 .WithMessage("Permission to destination Country must not be expired"); | ||||||
|  |  | ||||||
|  |             RuleFor(p => p!.Issuer) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Issuer of permission for destination Country can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.IssuerNameLength) | ||||||
|  |                 .WithMessage($"Issuer of permission to destination Country length must be less than {ConfigurationConstraints.IssuerNameLength}"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,25 @@ | |||||||
|  | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
|  | using Domains; | ||||||
|  | using Domains.VisaApplicationDomain; | ||||||
|  | using FluentValidation; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.VisaApplications.Requests.Validation | ||||||
|  | { | ||||||
|  |     public class ReentryPermitValidator : AbstractValidator<ReentryPermit?> | ||||||
|  |     { | ||||||
|  |         public ReentryPermitValidator(IDateTimeProvider dateTimeProvider) | ||||||
|  |         { | ||||||
|  |             RuleFor(p => p!.Number) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Re-entry permit number can not be empty") | ||||||
|  |                 .MaximumLength(ConfigurationConstraints.ReentryPermitNumberLength) | ||||||
|  |                 .WithMessage($"Re-entry permit number length must be less than {ConfigurationConstraints.ReentryPermitNumberLength}"); | ||||||
|  |  | ||||||
|  |             RuleFor(p => p!.ExpirationDate) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Re-entry permit expiration date can not be empty") | ||||||
|  |                 .GreaterThan(dateTimeProvider.Now()) | ||||||
|  |                 .WithMessage("Re-entry permit must not be expired"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,54 @@ | |||||||
|  | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
|  | using ApplicationLayer.Services.Applicants.NeededServices; | ||||||
|  | using Domains; | ||||||
|  | using Domains.VisaApplicationDomain; | ||||||
|  | using FluentValidation; | ||||||
|  |  | ||||||
|  | namespace ApplicationLayer.Services.VisaApplications.Requests.Validation | ||||||
|  | { | ||||||
|  |     public class VisaApplicationCreateRequestValidator : AbstractValidator<VisaApplicationCreateRequest> | ||||||
|  |     { | ||||||
|  |         public VisaApplicationCreateRequestValidator( | ||||||
|  |             IValidator<ReentryPermit?> reentryPermitValidator, | ||||||
|  |             IValidator<PastVisa> pastVisaValidator, | ||||||
|  |             IValidator<PermissionToDestCountry?> permissionToDestCountryValidator, | ||||||
|  |             IValidator<PastVisit> pastVisitValidator, | ||||||
|  |             IApplicantsRepository applicants, | ||||||
|  |             IUserIdProvider userIdProvider) | ||||||
|  |         { | ||||||
|  |             RuleFor(r => r.ReentryPermit) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Non-residents must provide re-entry permission") | ||||||
|  |                 .SetValidator(reentryPermitValidator) | ||||||
|  |                 .WhenAsync(async (r, ct) => | ||||||
|  |                     await applicants.IsApplicantNonResidentByUserId(userIdProvider.GetUserId(), ct)); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.DestinationCountry) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Destination country can not be empty"); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.VisaCategory) | ||||||
|  |                 .IsInEnum(); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.RequestedNumberOfEntries) | ||||||
|  |                 .IsInEnum(); | ||||||
|  |  | ||||||
|  |             RuleFor(r => r.ValidDaysRequested) | ||||||
|  |                 .GreaterThan(0) | ||||||
|  |                 .WithMessage($"Valid days requested should be positive number and less than {ConfigurationConstraints.MaxValidDays}") | ||||||
|  |                 .LessThanOrEqualTo(ConfigurationConstraints.MaxValidDays) | ||||||
|  |                 .WithMessage($"Valid days requested must be less than or equal to {ConfigurationConstraints.MaxValidDays}"); | ||||||
|  |  | ||||||
|  |             RuleForEach(r => r.PastVisas) | ||||||
|  |                 .SetValidator(pastVisaValidator); | ||||||
|  |  | ||||||
|  |             When(r => r.VisaCategory == VisaCategory.Transit, | ||||||
|  |                 () => | ||||||
|  |                     RuleFor(r => r.PermissionToDestCountry) | ||||||
|  |                         .SetValidator(permissionToDestCountryValidator)); | ||||||
|  |  | ||||||
|  |             RuleForEach(r => r.PastVisits) | ||||||
|  |                 .SetValidator(pastVisitValidator); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -4,7 +4,7 @@ namespace ApplicationLayer.Services.VisaApplications.Requests; | |||||||
|  |  | ||||||
| /// Model of visa request from user | /// Model of visa request from user | ||||||
| public record VisaApplicationCreateRequest( | public record VisaApplicationCreateRequest( | ||||||
|     ReentryPermit ReentryPermit, |     ReentryPermit? ReentryPermit, | ||||||
|     string DestinationCountry, |     string DestinationCountry, | ||||||
|     VisaCategory VisaCategory, |     VisaCategory VisaCategory, | ||||||
|     bool IsForGroup, |     bool IsForGroup, | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| namespace Infrastructure.Database | namespace Domains | ||||||
| { | { | ||||||
|     public static class ConfigurationConstraints |     public static class ConfigurationConstraints | ||||||
|     { |     { | ||||||
| @@ -13,8 +13,12 @@ | |||||||
|         public const int NameLength = 50; |         public const int NameLength = 50; | ||||||
|         public const int BuildingNumberLength = 10; |         public const int BuildingNumberLength = 10; | ||||||
|         public const int PassportNumberLength = 20; |         public const int PassportNumberLength = 20; | ||||||
|         public const int PhoneNumberLength = 15; |         public const int PhoneNumberLength = 13; | ||||||
|  |         public const int PhoneNumberMinLength = 11; | ||||||
|         public const int EmailLength = 254; |         public const int EmailLength = 254; | ||||||
|         public const int PasswordLength = 50; |         public const int PasswordLength = 50; | ||||||
|  |         public const int ApplicantMinAge = 14; | ||||||
|  |         public const int JobTitleLength = 50; | ||||||
|  |         public const int MaxValidDays = 90; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -6,4 +6,8 @@ | |||||||
|         <Nullable>enable</Nullable> |         <Nullable>enable</Nullable> | ||||||
|     </PropertyGroup> |     </PropertyGroup> | ||||||
|  |  | ||||||
|  |     <ItemGroup> | ||||||
|  |       <PackageReference Include="AutoMapper" Version="13.0.1" /> | ||||||
|  |     </ItemGroup> | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | using ApplicationLayer.Services.Applicants.Models; | ||||||
|  | using ApplicationLayer.Services.AuthServices.Requests; | ||||||
|  | using AutoMapper; | ||||||
|  | using Domains.ApplicantDomain; | ||||||
|  |  | ||||||
|  | namespace Infrastructure.Automapper.Profiles | ||||||
|  | { | ||||||
|  |     public class ApplicantProfile : Profile | ||||||
|  |     { | ||||||
|  |         public ApplicantProfile() | ||||||
|  |         { | ||||||
|  |             CreateMap<Applicant, ApplicantModel>(MemberList.Destination); | ||||||
|  |  | ||||||
|  |             CreateMap<RegisterApplicantRequest, Applicant>(MemberList.Destination) | ||||||
|  |                 .ForMember(a => a.UserId, opts => opts.Ignore()) | ||||||
|  |                 .ForMember(a => a.Name, | ||||||
|  |                     opts => opts.MapFrom(r => r.ApplicantName)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | using ApplicationLayer.Services.Applicants.Models; | ||||||
|  | using AutoMapper; | ||||||
|  | using Domains.ApplicantDomain; | ||||||
|  |  | ||||||
|  | namespace Infrastructure.Automapper.Profiles | ||||||
|  | { | ||||||
|  |     public class PlaceOfWorkProfile : Profile | ||||||
|  |     { | ||||||
|  |         public PlaceOfWorkProfile() | ||||||
|  |         { | ||||||
|  |             CreateMap<PlaceOfWorkModel, PlaceOfWork>(MemberList.Destination) | ||||||
|  |                 .ForMember(p => p.Id, | ||||||
|  |                     opts => opts.UseDestinationValue()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | using ApplicationLayer.Services.AuthServices.Common; | ||||||
|  | using AutoMapper; | ||||||
|  | using Domains.Users; | ||||||
|  |  | ||||||
|  | namespace Infrastructure.Automapper.Profiles | ||||||
|  | { | ||||||
|  |     public class UserProfile : Profile | ||||||
|  |     { | ||||||
|  |         public UserProfile() | ||||||
|  |         { | ||||||
|  |             CreateMap<AuthData, User>(MemberList.Destination) | ||||||
|  |                 .ForMember(u => u.Role, | ||||||
|  |                     opts => opts.Ignore()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,25 @@ | |||||||
|  | using ApplicationLayer.Services.VisaApplications.Models; | ||||||
|  | using ApplicationLayer.Services.VisaApplications.Requests; | ||||||
|  | using AutoMapper; | ||||||
|  | using Domains.VisaApplicationDomain; | ||||||
|  |  | ||||||
|  | namespace Infrastructure.Automapper.Profiles | ||||||
|  | { | ||||||
|  |     public class VisaApplicationProfile : Profile | ||||||
|  |     { | ||||||
|  |         public VisaApplicationProfile() | ||||||
|  |         { | ||||||
|  |             CreateMap<VisaApplication, VisaApplicationModelForApplicant>(MemberList.Destination); | ||||||
|  |  | ||||||
|  |             CreateMap<VisaApplication, VisaApplicationModelForAuthority>(MemberList.Destination) | ||||||
|  |                 .ForMember(model => model.Applicant, | ||||||
|  |                     opts => opts.Ignore()); | ||||||
|  |  | ||||||
|  |             CreateMap<VisaApplicationCreateRequest, VisaApplication>(MemberList.Destination) | ||||||
|  |                 .ForMember(va => va.RequestDate, | ||||||
|  |                 opts => opts.Ignore()) | ||||||
|  |                 .ForMember(va => va.ApplicantId, | ||||||
|  |                 opts => opts.Ignore()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								SchengenVisaApi/Infrastructure/Common/UserIdProvider.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								SchengenVisaApi/Infrastructure/Common/UserIdProvider.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | using System.Security.Claims; | ||||||
|  | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
|  | using Microsoft.AspNetCore.Http; | ||||||
|  |  | ||||||
|  | namespace Infrastructure.Common | ||||||
|  | { | ||||||
|  |     public class UserIdProvider(IHttpContextAccessor contextAccessor) : IUserIdProvider | ||||||
|  |     { | ||||||
|  |         Guid IUserIdProvider.GetUserId() | ||||||
|  |         { | ||||||
|  |             var claim = contextAccessor.HttpContext!.User.Claims.SingleOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier); | ||||||
|  |             if (claim is null) | ||||||
|  |             { | ||||||
|  |                 throw new InvalidOperationException("UserIdProvider call for request with no authorization"); | ||||||
|  |             } | ||||||
|  |             return Guid.Parse(claim.Value); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| using Domains.ApplicantDomain; | using Domains; | ||||||
|  | using Domains.ApplicantDomain; | ||||||
| using Domains.Users; | using Domains.Users; | ||||||
| using Microsoft.EntityFrameworkCore; | using Microsoft.EntityFrameworkCore; | ||||||
| using Microsoft.EntityFrameworkCore.Metadata.Builders; | using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||||||
| @@ -31,5 +32,9 @@ public class ApplicantConfiguration : IEntityTypeConfiguration<Applicant> | |||||||
|         entity.Property(a => a.CityOfBirth) |         entity.Property(a => a.CityOfBirth) | ||||||
|             .IsUnicode(false) |             .IsUnicode(false) | ||||||
|             .HasMaxLength(ConfigurationConstraints.CityNameLength); |             .HasMaxLength(ConfigurationConstraints.CityNameLength); | ||||||
|  |  | ||||||
|  |         entity.Property(a => a.JobTitle) | ||||||
|  |             .IsUnicode(false) | ||||||
|  |             .HasMaxLength(ConfigurationConstraints.JobTitleLength); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using Domains.ApplicantDomain; | using Domains; | ||||||
|  | using Domains.ApplicantDomain; | ||||||
| using Microsoft.EntityFrameworkCore; | using Microsoft.EntityFrameworkCore; | ||||||
| using Microsoft.EntityFrameworkCore.Metadata.Builders; | using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,7 +18,8 @@ public sealed class ApplicantsRepository(IGenericReader reader, IGenericWriter w | |||||||
|             .Include(a => a.PlaceOfWork); |             .Include(a => a.PlaceOfWork); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async Task<Applicant> IApplicantsRepository.FindByUserIdAsync(Guid userId, CancellationToken cancellationToken) |     /// <inheritdoc cref="IApplicantsRepository.FindByUserIdAsync"/> | ||||||
|  |     public async Task<Applicant> FindByUserIdAsync(Guid userId, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var result = await LoadDomain().SingleOrDefaultAsync(a => a.UserId == userId, cancellationToken); |         var result = await LoadDomain().SingleOrDefaultAsync(a => a.UserId == userId, cancellationToken); | ||||||
|         return result ?? throw new ApplicantNotFoundByUserIdException(); |         return result ?? throw new ApplicantNotFoundByUserIdException(); | ||||||
| @@ -29,4 +30,10 @@ public sealed class ApplicantsRepository(IGenericReader reader, IGenericWriter w | |||||||
|         var result = await base.LoadDomain().SingleOrDefaultAsync(a => a.UserId == userId, cancellationToken); |         var result = await base.LoadDomain().SingleOrDefaultAsync(a => a.UserId == userId, cancellationToken); | ||||||
|         return result?.Id ?? throw new ApplicantNotFoundByUserIdException(); |         return result?.Id ?? throw new ApplicantNotFoundByUserIdException(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async Task<bool> IApplicantsRepository.IsApplicantNonResidentByUserId(Guid userId, CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         var applicant = await FindByUserIdAsync(userId, cancellationToken); | ||||||
|  |         return applicant.IsNonResident; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using Domains.Users; | using Domains; | ||||||
|  | using Domains.Users; | ||||||
| using Microsoft.EntityFrameworkCore; | using Microsoft.EntityFrameworkCore; | ||||||
| using Microsoft.EntityFrameworkCore.Metadata.Builders; | using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using Domains.ApplicantDomain; | using Domains; | ||||||
|  | using Domains.ApplicantDomain; | ||||||
| using Domains.VisaApplicationDomain; | using Domains.VisaApplicationDomain; | ||||||
| using Microsoft.EntityFrameworkCore; | using Microsoft.EntityFrameworkCore; | ||||||
| using Microsoft.EntityFrameworkCore.Metadata.Builders; | using Microsoft.EntityFrameworkCore.Metadata.Builders; | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using ApplicationLayer.InfrastructureServicesInterfaces; | using System.Reflection; | ||||||
|  | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
| using ApplicationLayer.Services.Applicants.NeededServices; | using ApplicationLayer.Services.Applicants.NeededServices; | ||||||
| using ApplicationLayer.Services.AuthServices.NeededServices; | using ApplicationLayer.Services.AuthServices.NeededServices; | ||||||
| using ApplicationLayer.Services.VisaApplications.NeededServices; | using ApplicationLayer.Services.VisaApplications.NeededServices; | ||||||
| @@ -37,6 +38,11 @@ public static class DependencyInjection | |||||||
|  |  | ||||||
|         services.AddSingleton<IDateTimeProvider, DateTimeProvider>(); |         services.AddSingleton<IDateTimeProvider, DateTimeProvider>(); | ||||||
|  |  | ||||||
|  |         services.AddHttpContextAccessor(); | ||||||
|  |         services.AddScoped<IUserIdProvider, UserIdProvider>(); | ||||||
|  |  | ||||||
|  |         services.AddAutoMapper(Assembly.GetExecutingAssembly()); | ||||||
|  |  | ||||||
|         return services; |         return services; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,8 +1,11 @@ | |||||||
| using ApplicationLayer.Services.ApprovingAuthorities; | using ApplicationLayer.Services.AuthServices.Common; | ||||||
| using ApplicationLayer.Services.AuthServices.LoginService; | using ApplicationLayer.Services.AuthServices.LoginService; | ||||||
| using ApplicationLayer.Services.AuthServices.RegisterService; | using ApplicationLayer.Services.AuthServices.RegisterService; | ||||||
| using ApplicationLayer.Services.AuthServices.Requests; | using ApplicationLayer.Services.AuthServices.Requests; | ||||||
|  | using ApplicationLayer.Services.Users; | ||||||
|  | using ApplicationLayer.Services.Users.Requests; | ||||||
| using Domains.Users; | using Domains.Users; | ||||||
|  | using FluentValidation; | ||||||
| using Microsoft.AspNetCore.Authorization; | using Microsoft.AspNetCore.Authorization; | ||||||
| using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||||
| using SchengenVisaApi.Common; | using SchengenVisaApi.Common; | ||||||
| @@ -15,16 +18,21 @@ namespace SchengenVisaApi.Controllers | |||||||
|     public class UsersController( |     public class UsersController( | ||||||
|         IRegisterService registerService, |         IRegisterService registerService, | ||||||
|         ILoginService loginService, |         ILoginService loginService, | ||||||
|         IUsersService authorityService) : VisaApiControllerBase |         IUsersService usersService, | ||||||
|  |         IValidator<RegisterApplicantRequest> registerApplicantRequestValidator, | ||||||
|  |         IValidator<AuthData> authDataValidator) : ControllerBase | ||||||
|     { |     { | ||||||
|         /// <summary> Adds applicant with user account to DB </summary> |         /// <summary> Adds applicant with user account to DB </summary> | ||||||
|         [HttpPost] |         [HttpPost] | ||||||
|         [ProducesResponseType(StatusCodes.Status200OK)] |         [ProducesResponseType(StatusCodes.Status200OK)] | ||||||
|         [ProducesResponseType(StatusCodes.Status409Conflict)] |         [ProducesResponseType(StatusCodes.Status409Conflict)] | ||||||
|  |         [ProducesResponseType(StatusCodes.Status400BadRequest)] | ||||||
|         [Route("register")] |         [Route("register")] | ||||||
|         public async Task<IActionResult> Register(RegisterApplicantRequest request, CancellationToken cancellationToken) |         public async Task<IActionResult> Register(RegisterApplicantRequest request, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             await registerService.Register(request, cancellationToken); |             await registerApplicantRequestValidator.ValidateAndThrowAsync(request, cancellationToken); | ||||||
|  |  | ||||||
|  |             await registerService.RegisterApplicant(request, cancellationToken); | ||||||
|             return Ok(); |             return Ok(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -35,10 +43,13 @@ namespace SchengenVisaApi.Controllers | |||||||
|         [ProducesResponseType(StatusCodes.Status409Conflict)] |         [ProducesResponseType(StatusCodes.Status409Conflict)] | ||||||
|         [ProducesResponseType(StatusCodes.Status403Forbidden)] |         [ProducesResponseType(StatusCodes.Status403Forbidden)] | ||||||
|         [ProducesResponseType(StatusCodes.Status401Unauthorized)] |         [ProducesResponseType(StatusCodes.Status401Unauthorized)] | ||||||
|  |         [ProducesResponseType(StatusCodes.Status400BadRequest)] | ||||||
|         [Route("authorities")] |         [Route("authorities")] | ||||||
|         [Authorize(policy: PolicyConstants.AdminPolicy)] |         [Authorize(policy: PolicyConstants.AdminPolicy)] | ||||||
|         public async Task<IActionResult> RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken) |         public async Task<IActionResult> RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|  |             await authDataValidator.ValidateAndThrowAsync(request.AuthData, cancellationToken); | ||||||
|  |  | ||||||
|             await registerService.RegisterAuthority(request, cancellationToken); |             await registerService.RegisterAuthority(request, cancellationToken); | ||||||
|             return Ok(); |             return Ok(); | ||||||
|         } |         } | ||||||
| @@ -50,7 +61,7 @@ namespace SchengenVisaApi.Controllers | |||||||
|         [Route("login")] |         [Route("login")] | ||||||
|         public async Task<IActionResult> Login(string email, string password, CancellationToken cancellationToken) |         public async Task<IActionResult> Login(string email, string password, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var result = await loginService.LoginAsync(new UserLoginRequest(email, password), cancellationToken); |             var result = await loginService.LoginAsync(email, password, cancellationToken); | ||||||
|             return Ok(result); |             return Ok(result); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -64,7 +75,7 @@ namespace SchengenVisaApi.Controllers | |||||||
|         [Authorize(policy: PolicyConstants.AdminPolicy)] |         [Authorize(policy: PolicyConstants.AdminPolicy)] | ||||||
|         public async Task<IActionResult> GetAuthorityAccounts(CancellationToken cancellationToken) |         public async Task<IActionResult> GetAuthorityAccounts(CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             var result = await authorityService.GetAuthoritiesAccountsAsync(cancellationToken); |             var result = await usersService.GetAuthoritiesAccountsAsync(cancellationToken); | ||||||
|             return Ok(result); |             return Ok(result); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -75,12 +86,14 @@ namespace SchengenVisaApi.Controllers | |||||||
|         [ProducesResponseType(StatusCodes.Status404NotFound)] |         [ProducesResponseType(StatusCodes.Status404NotFound)] | ||||||
|         [ProducesResponseType(StatusCodes.Status403Forbidden)] |         [ProducesResponseType(StatusCodes.Status403Forbidden)] | ||||||
|         [ProducesResponseType(StatusCodes.Status401Unauthorized)] |         [ProducesResponseType(StatusCodes.Status401Unauthorized)] | ||||||
|  |         [ProducesResponseType(StatusCodes.Status400BadRequest)] | ||||||
|         [Route("authorities/{authorityAccountId:guid}")] |         [Route("authorities/{authorityAccountId:guid}")] | ||||||
|         [Authorize(policy: PolicyConstants.AdminPolicy)] |         [Authorize(policy: PolicyConstants.AdminPolicy)] | ||||||
|         //todo replace args with ChangeAuthorityAuthDataRequest or something |         public async Task<IActionResult> ChangeAuthorityAuthData(Guid authorityAccountId, AuthData authData, CancellationToken cancellationToken) | ||||||
|         public async Task<IActionResult> ChangeAuthorityAuthData(Guid authorityAccountId, RegisterRequest authData, CancellationToken cancellationToken) |  | ||||||
|         { |         { | ||||||
|             await authorityService.ChangeAccountAuthDataAsync(authorityAccountId, authData, cancellationToken); |             await authDataValidator.ValidateAndThrowAsync(authData, cancellationToken); | ||||||
|  |  | ||||||
|  |             await usersService.ChangeAccountAuthDataAsync(new ChangeUserAuthDataRequest(authorityAccountId, authData), cancellationToken); | ||||||
|             return Ok(); |             return Ok(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -95,7 +108,7 @@ namespace SchengenVisaApi.Controllers | |||||||
|         [Authorize(policy: PolicyConstants.AdminPolicy)] |         [Authorize(policy: PolicyConstants.AdminPolicy)] | ||||||
|         public async Task<IActionResult> RemoveAuthorityAccount(Guid authorityAccountId, CancellationToken cancellationToken) |         public async Task<IActionResult> RemoveAuthorityAccount(Guid authorityAccountId, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|             await authorityService.RemoveUserAccount(authorityAccountId, cancellationToken); |             await usersService.RemoveUserAccount(authorityAccountId, cancellationToken); | ||||||
|             return Ok(); |             return Ok(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,12 +0,0 @@ | |||||||
| using System.Security.Claims; |  | ||||||
| using Microsoft.AspNetCore.Mvc; |  | ||||||
|  |  | ||||||
| namespace SchengenVisaApi.Controllers |  | ||||||
| { |  | ||||||
|     /// Base controller class for api controllers in project |  | ||||||
|     public abstract class VisaApiControllerBase : ControllerBase |  | ||||||
|     { |  | ||||||
|         /// Returns identifier of authenticated user |  | ||||||
|         protected Guid GetUserId() => Guid.Parse(HttpContext.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,6 +1,8 @@ | |||||||
|  | using ApplicationLayer.InfrastructureServicesInterfaces; | ||||||
| using ApplicationLayer.Services.VisaApplications.Handlers; | using ApplicationLayer.Services.VisaApplications.Handlers; | ||||||
| using ApplicationLayer.Services.VisaApplications.Models; | using ApplicationLayer.Services.VisaApplications.Models; | ||||||
| using ApplicationLayer.Services.VisaApplications.Requests; | using ApplicationLayer.Services.VisaApplications.Requests; | ||||||
|  | using FluentValidation; | ||||||
| using Microsoft.AspNetCore.Authorization; | using Microsoft.AspNetCore.Authorization; | ||||||
| using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||||
| using SchengenVisaApi.Common; | using SchengenVisaApi.Common; | ||||||
| @@ -10,7 +12,10 @@ namespace SchengenVisaApi.Controllers; | |||||||
| /// <summary> Controller for <see cref="Domains.VisaApplicationDomain"/> </summary> | /// <summary> Controller for <see cref="Domains.VisaApplicationDomain"/> </summary> | ||||||
| [ApiController] | [ApiController] | ||||||
| [Route("visaApplications")] | [Route("visaApplications")] | ||||||
| public class VisaApplicationController(IVisaApplicationRequestsHandler visaApplicationRequestsHandler) : VisaApiControllerBase | public class VisaApplicationController( | ||||||
|  |     IVisaApplicationRequestsHandler visaApplicationRequestsHandler, | ||||||
|  |     IUserIdProvider userIdProvider, | ||||||
|  |     IValidator<VisaApplicationCreateRequest> visaApplicationCreateRequestValidator) : ControllerBase | ||||||
| { | { | ||||||
|     /// <summary> Returns all applications from DB </summary> |     /// <summary> Returns all applications from DB </summary> | ||||||
|     /// <remarks> Accessible only for approving authorities </remarks> |     /// <remarks> Accessible only for approving authorities </remarks> | ||||||
| @@ -36,7 +41,7 @@ public class VisaApplicationController(IVisaApplicationRequestsHandler visaAppli | |||||||
|     [Route("OfApplicant")] |     [Route("OfApplicant")] | ||||||
|     public async Task<IActionResult> GetForApplicant(CancellationToken cancellationToken) |     public async Task<IActionResult> GetForApplicant(CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var userId = GetUserId(); |         var userId = userIdProvider.GetUserId(); | ||||||
|         var result = await visaApplicationRequestsHandler.GetForApplicantAsync(userId, cancellationToken); |         var result = await visaApplicationRequestsHandler.GetForApplicantAsync(userId, cancellationToken); | ||||||
|         return Ok(result); |         return Ok(result); | ||||||
|     } |     } | ||||||
| @@ -48,10 +53,13 @@ public class VisaApplicationController(IVisaApplicationRequestsHandler visaAppli | |||||||
|     [ProducesResponseType(StatusCodes.Status403Forbidden)] |     [ProducesResponseType(StatusCodes.Status403Forbidden)] | ||||||
|     [ProducesResponseType(StatusCodes.Status401Unauthorized)] |     [ProducesResponseType(StatusCodes.Status401Unauthorized)] | ||||||
|     [ProducesResponseType(StatusCodes.Status404NotFound)] |     [ProducesResponseType(StatusCodes.Status404NotFound)] | ||||||
|  |     [ProducesResponseType(StatusCodes.Status400BadRequest)] | ||||||
|     [Authorize(policy: PolicyConstants.ApplicantPolicy)] |     [Authorize(policy: PolicyConstants.ApplicantPolicy)] | ||||||
|     public async Task<IActionResult> Create(VisaApplicationCreateRequest request, CancellationToken cancellationToken) |     public async Task<IActionResult> Create(VisaApplicationCreateRequest request, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var userId = GetUserId(); |         await visaApplicationCreateRequestValidator.ValidateAndThrowAsync(request, cancellationToken); | ||||||
|  |  | ||||||
|  |         var userId = userIdProvider.GetUserId(); | ||||||
|         await visaApplicationRequestsHandler.HandleCreateRequestAsync(userId, request, cancellationToken); |         await visaApplicationRequestsHandler.HandleCreateRequestAsync(userId, request, cancellationToken); | ||||||
|         return Ok(); |         return Ok(); | ||||||
|     } |     } | ||||||
| @@ -67,7 +75,7 @@ public class VisaApplicationController(IVisaApplicationRequestsHandler visaAppli | |||||||
|     [Route("{applicationId:guid}")] |     [Route("{applicationId:guid}")] | ||||||
|     public async Task<IActionResult> CloseApplication(Guid applicationId, CancellationToken cancellationToken) |     public async Task<IActionResult> CloseApplication(Guid applicationId, CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         var userId = GetUserId(); |         var userId = userIdProvider.GetUserId(); | ||||||
|         await visaApplicationRequestsHandler.HandleCloseRequestAsync(userId, applicationId, cancellationToken); |         await visaApplicationRequestsHandler.HandleCloseRequestAsync(userId, applicationId, cancellationToken); | ||||||
|         return Ok(); |         return Ok(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| using ApplicationLayer.Services.AuthServices.LoginService.Exceptions; | using ApplicationLayer.Services.AuthServices.LoginService.Exceptions; | ||||||
| using ApplicationLayer.Services.GeneralExceptions; | using ApplicationLayer.Services.GeneralExceptions; | ||||||
| using ApplicationLayer.Services.VisaApplications.Exceptions; | using ApplicationLayer.Services.VisaApplications.Exceptions; | ||||||
|  | using FluentValidation; | ||||||
| using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||||
| using Microsoft.AspNetCore.Mvc.Filters; | using Microsoft.AspNetCore.Mvc.Filters; | ||||||
|  |  | ||||||
| @@ -16,8 +17,16 @@ namespace SchengenVisaApi.ExceptionFilters | |||||||
|             var exception = context.Exception; |             var exception = context.Exception; | ||||||
|             var problemDetails = new ProblemDetails(); |             var problemDetails = new ProblemDetails(); | ||||||
|  |  | ||||||
|             if (exception is ApiException) |             switch (exception) | ||||||
|             { |             { | ||||||
|  |                 case ValidationException validationException: | ||||||
|  |                     problemDetails.Extensions.Add("Errors", validationException.Errors.Select(e => e.ErrorMessage)); | ||||||
|  |                     problemDetails.Detail = "Validation errors occured"; | ||||||
|  |                     problemDetails.Status = StatusCodes.Status400BadRequest; | ||||||
|  |                     problemDetails.Title = "Bad request"; | ||||||
|  |                     problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1"; | ||||||
|  |                     break; | ||||||
|  |                 case ApiException: | ||||||
|                     problemDetails.Detail = exception.Message; |                     problemDetails.Detail = exception.Message; | ||||||
|                     switch (exception) |                     switch (exception) | ||||||
|                     { |                     { | ||||||
| @@ -47,12 +56,13 @@ namespace SchengenVisaApi.ExceptionFilters | |||||||
|                             problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1"; |                             problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1"; | ||||||
|                             break; |                             break; | ||||||
|                     } |                     } | ||||||
|             } |  | ||||||
|             else |                     break; | ||||||
|             { |                 default: | ||||||
|                     problemDetails.Status = StatusCodes.Status500InternalServerError; |                     problemDetails.Status = StatusCodes.Status500InternalServerError; | ||||||
|                     problemDetails.Title = "An unhandled error occured"; |                     problemDetails.Title = "An unhandled error occured"; | ||||||
|                     problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1"; |                     problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1"; | ||||||
|  |                     break; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             await Results.Problem(problemDetails).ExecuteAsync(context.HttpContext); |             await Results.Problem(problemDetails).ExecuteAsync(context.HttpContext); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 prtsie
					prtsie