Added validation and fixed errors
This commit is contained in:
		| @@ -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(); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -3,5 +3,5 @@ using ApplicationLayer.Services.AuthServices.Requests; | |||||||
|  |  | ||||||
| namespace ApplicationLayer.Services.AuthServices.RegisterService.Exceptions | namespace ApplicationLayer.Services.AuthServices.RegisterService.Exceptions | ||||||
| { | { | ||||||
|     public class UserAlreadyExistsException(RegisterRequest request) : AlreadyExistsException($"User with email '{request.Email}' already exists"); |     public class UserAlreadyExistsException(RegisterRequest request) : AlreadyExistsException($"User with email '{request.AuthData.Email}' already exists"); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| 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 AutoMapper; | ||||||
| using Domains.ApplicantDomain; | using Domains.ApplicantDomain; | ||||||
| @@ -18,33 +17,11 @@ namespace ApplicationLayer.Services.AuthServices.RegisterService | |||||||
|     { |     { | ||||||
|         async Task IRegisterService.RegisterApplicant(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) |  | ||||||
|             { |  | ||||||
|                 throw new UserAlreadyExistsException(request); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             var user = mapper.Map<User>(request); |  | ||||||
|             user.Role = Role.Applicant; |             user.Role = Role.Applicant; | ||||||
|  |  | ||||||
|             var applicant = new Applicant |             var applicant = mapper.Map<Applicant>(request); | ||||||
|             { |             applicant.UserId = user.Id; | ||||||
|                 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); | ||||||
| @@ -54,13 +31,7 @@ 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) |  | ||||||
|             { |  | ||||||
|                 throw new UserAlreadyExistsException(request); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             var user = mapper.Map<User>(request); |  | ||||||
|             user.Role = Role.ApprovingAuthority; |             user.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 +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); | ||||||
| @@ -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,63 @@ | |||||||
|  | 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) | ||||||
|  |         { | ||||||
|  |             //todo fix | ||||||
|  |             WhenAsync( | ||||||
|  |                 async (_, ct) => | ||||||
|  |                 { | ||||||
|  |                     return await applicants.IsApplicantNonResidentByUserId(userIdProvider.GetUserId(), ct); | ||||||
|  |                 }, | ||||||
|  |                 () => | ||||||
|  |                 { | ||||||
|  |                     RuleFor(r => r.ReentryPermit) | ||||||
|  |                         .NotEmpty() | ||||||
|  |                         .WithMessage("Non-residents must provide re-entry permission") | ||||||
|  |                         .SetValidator(reentryPermitValidator); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             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) | ||||||
|  |                 .NotEmpty() | ||||||
|  |                 .WithMessage("Valid days requested can not be empty") | ||||||
|  |                 .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; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| using ApplicationLayer.Services.Applicants.Models; | using ApplicationLayer.Services.Applicants.Models; | ||||||
|  | using ApplicationLayer.Services.AuthServices.Requests; | ||||||
| using AutoMapper; | using AutoMapper; | ||||||
| using Domains.ApplicantDomain; | using Domains.ApplicantDomain; | ||||||
|  |  | ||||||
| @@ -9,6 +10,11 @@ namespace Infrastructure.Automapper.Profiles | |||||||
|         public ApplicantProfile() |         public ApplicantProfile() | ||||||
|         { |         { | ||||||
|             CreateMap<Applicant, ApplicantModel>(MemberList.Destination); |             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()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| using ApplicationLayer.Services.AuthServices.Requests; | using ApplicationLayer.Services.AuthServices.Common; | ||||||
| using AutoMapper; | using AutoMapper; | ||||||
| using Domains.Users; | using Domains.Users; | ||||||
|  |  | ||||||
| @@ -8,11 +8,7 @@ namespace Infrastructure.Automapper.Profiles | |||||||
|     { |     { | ||||||
|         public UserProfile() |         public UserProfile() | ||||||
|         { |         { | ||||||
|             CreateMap<RegisterApplicantRequest, User>(MemberList.Destination) |             CreateMap<AuthData, User>(MemberList.Destination) | ||||||
|                 .ForMember(u => u.Role, |  | ||||||
|                     opts => opts.Ignore()); |  | ||||||
|  |  | ||||||
|             CreateMap<RegisterRequest, User>() |  | ||||||
|                 .ForMember(u => u.Role, |                 .ForMember(u => u.Role, | ||||||
|                     opts => opts.Ignore()); |                     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; | ||||||
|   | |||||||
| @@ -38,6 +38,9 @@ public static class DependencyInjection | |||||||
|  |  | ||||||
|         services.AddSingleton<IDateTimeProvider, DateTimeProvider>(); |         services.AddSingleton<IDateTimeProvider, DateTimeProvider>(); | ||||||
|  |  | ||||||
|  |         services.AddHttpContextAccessor(); | ||||||
|  |         services.AddScoped<IUserIdProvider, UserIdProvider>(); | ||||||
|  |  | ||||||
|         services.AddAutoMapper(Assembly.GetExecutingAssembly()); |         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,7 +18,9 @@ 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] | ||||||
| @@ -24,6 +29,8 @@ namespace SchengenVisaApi.Controllers | |||||||
|         [Route("register")] |         [Route("register")] | ||||||
|         public async Task<IActionResult> Register(RegisterApplicantRequest request, CancellationToken cancellationToken) |         public async Task<IActionResult> Register(RegisterApplicantRequest request, CancellationToken cancellationToken) | ||||||
|         { |         { | ||||||
|  |             await registerApplicantRequestValidator.ValidateAndThrowAsync(request, cancellationToken); | ||||||
|  |  | ||||||
|             await registerService.RegisterApplicant(request, cancellationToken); |             await registerService.RegisterApplicant(request, cancellationToken); | ||||||
|             return Ok(); |             return Ok(); | ||||||
|         } |         } | ||||||
| @@ -39,6 +46,8 @@ namespace SchengenVisaApi.Controllers | |||||||
|         [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 +59,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); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -62,10 +71,9 @@ namespace SchengenVisaApi.Controllers | |||||||
|         [ProducesResponseType(StatusCodes.Status401Unauthorized)] |         [ProducesResponseType(StatusCodes.Status401Unauthorized)] | ||||||
|         [Route("authorities")] |         [Route("authorities")] | ||||||
|         [Authorize(policy: PolicyConstants.AdminPolicy)] |         [Authorize(policy: PolicyConstants.AdminPolicy)] | ||||||
|         //todo return models |  | ||||||
|         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); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -78,10 +86,11 @@ namespace SchengenVisaApi.Controllers | |||||||
|         [ProducesResponseType(StatusCodes.Status401Unauthorized)] |         [ProducesResponseType(StatusCodes.Status401Unauthorized)] | ||||||
|         [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(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -96,7 +105,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,3 +1,4 @@ | |||||||
|  | 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; | ||||||
| @@ -10,7 +11,9 @@ 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) : 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 +39,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); | ||||||
|     } |     } | ||||||
| @@ -51,7 +54,7 @@ public class VisaApplicationController(IVisaApplicationRequestsHandler visaAppli | |||||||
|     [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(); |         var userId = userIdProvider.GetUserId(); | ||||||
|         await visaApplicationRequestsHandler.HandleCreateRequestAsync(userId, request, cancellationToken); |         await visaApplicationRequestsHandler.HandleCreateRequestAsync(userId, request, cancellationToken); | ||||||
|         return Ok(); |         return Ok(); | ||||||
|     } |     } | ||||||
| @@ -67,7 +70,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