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,43 +17,52 @@ 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)
|
||||||
{
|
{
|
||||||
problemDetails.Detail = exception.Message;
|
case ValidationException validationException:
|
||||||
switch (exception)
|
problemDetails.Extensions.Add("Errors", validationException.Errors.Select(e => e.ErrorMessage));
|
||||||
{
|
problemDetails.Detail = "Validation errors occured";
|
||||||
case EntityNotFoundException:
|
problemDetails.Status = StatusCodes.Status400BadRequest;
|
||||||
problemDetails.Status = StatusCodes.Status404NotFound;
|
problemDetails.Title = "Bad request";
|
||||||
problemDetails.Title = "Requested entity not found";
|
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.4";
|
break;
|
||||||
break;
|
case ApiException:
|
||||||
case IncorrectLoginDataException:
|
problemDetails.Detail = exception.Message;
|
||||||
problemDetails.Status = StatusCodes.Status403Forbidden;
|
switch (exception)
|
||||||
problemDetails.Title = "Auth failed";
|
{
|
||||||
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.3";
|
case EntityNotFoundException:
|
||||||
break;
|
problemDetails.Status = StatusCodes.Status404NotFound;
|
||||||
case AlreadyExistsException:
|
problemDetails.Title = "Requested entity not found";
|
||||||
problemDetails.Status = StatusCodes.Status409Conflict;
|
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4";
|
||||||
problemDetails.Title = "Already exists";
|
break;
|
||||||
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.8";
|
case IncorrectLoginDataException:
|
||||||
break;
|
problemDetails.Status = StatusCodes.Status403Forbidden;
|
||||||
case ApplicationAlreadyProcessedException:
|
problemDetails.Title = "Auth failed";
|
||||||
problemDetails.Status = StatusCodes.Status409Conflict;
|
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.3";
|
||||||
problemDetails.Title = "Already processed";
|
break;
|
||||||
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.8";
|
case AlreadyExistsException:
|
||||||
break;
|
problemDetails.Status = StatusCodes.Status409Conflict;
|
||||||
default:
|
problemDetails.Title = "Already exists";
|
||||||
problemDetails.Status = StatusCodes.Status400BadRequest;
|
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.8";
|
||||||
problemDetails.Title = "Bad request";
|
break;
|
||||||
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1";
|
case ApplicationAlreadyProcessedException:
|
||||||
break;
|
problemDetails.Status = StatusCodes.Status409Conflict;
|
||||||
}
|
problemDetails.Title = "Already processed";
|
||||||
}
|
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.8";
|
||||||
else
|
break;
|
||||||
{
|
default:
|
||||||
problemDetails.Status = StatusCodes.Status500InternalServerError;
|
problemDetails.Status = StatusCodes.Status400BadRequest;
|
||||||
problemDetails.Title = "An unhandled error occured";
|
problemDetails.Title = "Bad request";
|
||||||
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1";
|
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
problemDetails.Status = StatusCodes.Status500InternalServerError;
|
||||||
|
problemDetails.Title = "An unhandled error occured";
|
||||||
|
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