Added models for presentation layer with data annotations

This commit is contained in:
2024-08-29 12:49:47 +03:00
parent ce077ad6b9
commit 7c8631b3b4
45 changed files with 742 additions and 453 deletions

View File

@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
using Domains;
namespace ApplicationLayer.Services.Applicants.Models;
public class AddressModel
{
/// Country part of address
[Required]
[MaxLength(ConfigurationConstraints.CountryNameLength)]
public string Country { get; set; } = null!;
/// City part of address
[Required]
[MaxLength(ConfigurationConstraints.CityNameLength)]
public string City { get; set; } = null!;
/// Street part of address
[Required]
[MaxLength(ConfigurationConstraints.StreetNameLength)]
public string Street { get; set; } = null!;
/// Building part of address
[Required]
[MaxLength(ConfigurationConstraints.BuildingNumberLength)]
public string Building { get; set; } = null!;
}

View File

@@ -2,48 +2,49 @@
namespace ApplicationLayer.Services.Applicants.Models;
/// Model of <see cref="Applicant"/>
/// Model of
/// <see cref="Applicant" />
public class ApplicantModel
{
/// <inheritdoc cref="Applicant.Name"/>
/// <inheritdoc cref="Applicant.Name" />
public Name Name { get; set; } = null!;
/// <inheritdoc cref="Applicant.Passport"/>
/// <inheritdoc cref="Applicant.Passport" />
public Passport Passport { get; set; } = null!;
/// <inheritdoc cref="Applicant.BirthDate"/>
/// <inheritdoc cref="Applicant.BirthDate" />
public DateTime BirthDate { get; set; }
/// <inheritdoc cref="Applicant.CountryOfBirth"/>
/// <inheritdoc cref="Applicant.CountryOfBirth" />
public string CountryOfBirth { get; set; } = null!;
/// <inheritdoc cref="Applicant.CityOfBirth"/>
/// <inheritdoc cref="Applicant.CityOfBirth" />
public string CityOfBirth { get; set; } = null!;
/// <inheritdoc cref="Applicant.Citizenship"/>
/// <inheritdoc cref="Applicant.Citizenship" />
public string Citizenship { get; set; } = null!;
/// <inheritdoc cref="Applicant.CitizenshipByBirth"/>
/// <inheritdoc cref="Applicant.CitizenshipByBirth" />
public string CitizenshipByBirth { get; set; } = null!;
/// <inheritdoc cref="Applicant.Gender"/>
/// <inheritdoc cref="Applicant.Gender" />
public Gender Gender { get; set; }
/// <inheritdoc cref="Applicant.MaritalStatus"/>
/// <inheritdoc cref="Applicant.MaritalStatus" />
public MaritalStatus MaritalStatus { get; set; }
/// <inheritdoc cref="Applicant.FatherName"/>
/// <inheritdoc cref="Applicant.FatherName" />
public Name FatherName { get; set; } = null!;
/// <inheritdoc cref="Applicant.MotherName"/>
/// <inheritdoc cref="Applicant.MotherName" />
public Name MotherName { get; set; } = null!;
/// <inheritdoc cref="Applicant.JobTitle"/>
/// <inheritdoc cref="Applicant.JobTitle" />
public string JobTitle { get; set; } = null!;
/// <inheritdoc cref="Applicant.PlaceOfWork"/>
/// <inheritdoc cref="Applicant.PlaceOfWork" />
public PlaceOfWork PlaceOfWork { get; set; } = null!;
/// <inheritdoc cref="Applicant.IsNonResident"/>
/// <inheritdoc cref="Applicant.IsNonResident" />
public bool IsNonResident { get; set; }
}
}

View File

@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using Domains;
namespace ApplicationLayer.Services.Applicants.Models;
/// Model of name for presentation layer
public class NameModel
{
[Required]
[MaxLength(ConfigurationConstraints.NameLength)]
public string FirstName { get; set; } = null!;
[Required]
[MaxLength(ConfigurationConstraints.NameLength)]
public string Surname { get; set; } = null!;
[MaxLength(ConfigurationConstraints.NameLength)]
public string? Patronymic { get; set; }
}

View File

@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations;
using Domains;
namespace ApplicationLayer.Services.Applicants.Models;
/// Model of passport fpr presentation layer
public class PassportModel
{
/// Number of passport
[Required]
[MaxLength(ConfigurationConstraints.PassportNumberLength)]
public string Number { get; set; } = null!;
/// Issuing authority of passport
[Required]
[MaxLength(ConfigurationConstraints.IssuerNameLength)]
public string Issuer { get; set; } = null!;
/// Date of issue
[Required]
public DateTime IssueDate { get; set; }
/// Date when the passport expires
[Required]
public DateTime ExpirationDate { get; set; }
}

View File

@@ -1,15 +1,22 @@
using Domains.ApplicantDomain;
using System.ComponentModel.DataAnnotations;
using Domains;
namespace ApplicationLayer.Services.Applicants.Models;
public class PlaceOfWorkModel
{
/// Name of hirer
[Required]
[MaxLength(ConfigurationConstraints.PlaceOfWorkNameLength)]
public string Name { get; set; } = null!;
/// Address of hirer
public Address Address { get; set; } = null!;
[Required]
public AddressModel Address { get; set; } = null!;
/// Phone number of hirer
[Required]
[MaxLength(ConfigurationConstraints.PhoneNumberLength)]
[MinLength(ConfigurationConstraints.PhoneNumberMinLength)]
public string PhoneNum { get; set; } = null!;
}

View File

@@ -0,0 +1,26 @@
using Domains;
using FluentValidation;
namespace ApplicationLayer.Services.Applicants.Models.Validation;
public class NameModelValidator : AbstractValidator<NameModel>
{
public NameModelValidator()
{
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}");
}
}

View File

@@ -0,0 +1,35 @@
using ApplicationLayer.InfrastructureServicesInterfaces;
using Domains;
using FluentValidation;
namespace ApplicationLayer.Services.Applicants.Models.Validation;
public class PassportModelValidator : AbstractValidator<PassportModel>
{
public PassportModelValidator(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");
}
}

View File

@@ -0,0 +1,50 @@
using Domains;
using FluentValidation;
namespace ApplicationLayer.Services.Applicants.Models.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}");
}
}

View File

@@ -1,3 +1,15 @@
namespace ApplicationLayer.Services.AuthServices.Common;
using System.ComponentModel.DataAnnotations;
using Domains;
public record AuthData(string Email, string Password);
namespace ApplicationLayer.Services.AuthServices.Common;
public class AuthData
{
[Required]
[MaxLength(ConfigurationConstraints.EmailLength)]
public string Email { get; set; } = null!;
[Required]
[MaxLength(ConfigurationConstraints.PasswordLength)]
public string Password { get; set; } = null!;
}

View File

@@ -1,26 +1,27 @@
using ApplicationLayer.Services.AuthServices.LoginService.Exceptions;
using ApplicationLayer.Services.AuthServices.NeededServices;
using ApplicationLayer.Services.AuthServices.Requests;
using Domains.Users;
namespace ApplicationLayer.Services.AuthServices.LoginService;
public class DevelopmentLoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService
{
async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken)
async Task<string> ILoginService.LoginAsync(LoginRequest request, CancellationToken cancellationToken)
{
if (email == "admin@mail.ru" && password == "admin")
{
var admin = new User { Role = Role.Admin };
if (request.AuthData is { Email: "admin@mail.ru", Password: "admin" })
{
var admin = new User { Role = Role.Admin };
return tokenGenerator.CreateToken(admin);
}
var user = await users.FindByEmailAsync(email, cancellationToken);
if (user is null || user.Password != password)
{
throw new IncorrectLoginDataException();
}
return tokenGenerator.CreateToken(user);
return tokenGenerator.CreateToken(admin);
}
}
var user = await users.FindByEmailAsync(request.AuthData.Email, cancellationToken);
if (user is null || user.Password != request.AuthData.Password)
{
throw new IncorrectLoginDataException();
}
return tokenGenerator.CreateToken(user);
}
}

View File

@@ -1,9 +1,11 @@
namespace ApplicationLayer.Services.AuthServices.LoginService;
using ApplicationLayer.Services.AuthServices.Requests;
namespace ApplicationLayer.Services.AuthServices.LoginService;
/// Handles login requests
public interface ILoginService
{
/// Handle login request
/// <returns>JWT-token</returns>
Task<string> LoginAsync(string email, string password, CancellationToken cancellationToken);
}
Task<string> LoginAsync(LoginRequest request, CancellationToken cancellationToken);
}

View File

@@ -1,19 +1,20 @@
using ApplicationLayer.Services.AuthServices.LoginService.Exceptions;
using ApplicationLayer.Services.AuthServices.NeededServices;
using ApplicationLayer.Services.AuthServices.Requests;
namespace ApplicationLayer.Services.AuthServices.LoginService;
/// <inheritdoc cref="ILoginService"/>
/// <inheritdoc cref="ILoginService" />
public class LoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService
{
async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken)
async Task<string> ILoginService.LoginAsync(LoginRequest request, CancellationToken cancellationToken)
{
var user = await users.FindByEmailAsync(email, cancellationToken);
if (user is null || user.Password != password)
{
throw new IncorrectLoginDataException();
}
return tokenGenerator.CreateToken(user);
var user = await users.FindByEmailAsync(request.AuthData.Email, cancellationToken);
if (user is null || user.Password != request.AuthData.Password)
{
throw new IncorrectLoginDataException();
}
}
return tokenGenerator.CreateToken(user);
}
}

View File

@@ -17,7 +17,7 @@ public class RegisterService(
{
async Task IRegisterService.RegisterApplicant(RegisterApplicantRequest request, CancellationToken cancellationToken)
{
var user = mapper.Map<User>(request.AuthData);
var user = mapper.Map<User>(request.RegisterRequest.AuthData);
user.Role = Role.Applicant;
var applicant = mapper.Map<Applicant>(request);

View File

@@ -0,0 +1,9 @@
using System.ComponentModel.DataAnnotations;
using ApplicationLayer.Services.AuthServices.Common;
namespace ApplicationLayer.Services.AuthServices.Requests;
public class LoginRequest
{
[Required] public AuthData AuthData { get; set; } = null!;
}

View File

@@ -1,22 +1,49 @@
using ApplicationLayer.Services.Applicants.Models;
using ApplicationLayer.Services.AuthServices.Common;
using System.ComponentModel.DataAnnotations;
using ApplicationLayer.Services.Applicants.Models;
using Domains;
using Domains.ApplicantDomain;
namespace ApplicationLayer.Services.AuthServices.Requests;
public record RegisterApplicantRequest(
AuthData AuthData,
Name ApplicantName,
Passport Passport,
DateTime BirthDate,
string CityOfBirth,
string CountryOfBirth,
string Citizenship,
string CitizenshipByBirth,
Gender Gender,
MaritalStatus MaritalStatus,
Name FatherName,
Name MotherName,
string JobTitle,
PlaceOfWorkModel PlaceOfWork,
bool IsNonResident) : RegisterRequest(AuthData);
public record RegisterApplicantRequest
{
[Required] public RegisterRequest RegisterRequest { get; set; } = null!;
[Required] public NameModel ApplicantName { get; set; } = null!;
[Required] public PassportModel Passport { get; set; } = null!;
[Required] public DateTime BirthDate { get; set; }
[Required]
[MaxLength(ConfigurationConstraints.CityNameLength)]
public string CityOfBirth { get; set; } = null!;
[Required]
[MaxLength(ConfigurationConstraints.CountryNameLength)]
public string CountryOfBirth { get; set; } = null!;
[Required]
[MaxLength(ConfigurationConstraints.CitizenshipLength)]
public string Citizenship { get; set; } = null!;
[Required]
[MaxLength(ConfigurationConstraints.CitizenshipLength)]
public string CitizenshipByBirth { get; set; } = null!;
[Required] public Gender Gender { get; set; }
[Required] public MaritalStatus MaritalStatus { get; set; }
[Required] public NameModel FatherName { get; set; } = null!;
[Required] public NameModel MotherName { get; set; } = null!;
[Required]
[MaxLength(ConfigurationConstraints.JobTitleLength)]
public string JobTitle { get; set; } = null!;
[Required] public PlaceOfWorkModel PlaceOfWork { get; set; } = null!;
[Required] public bool IsNonResident { get; set; }
}

View File

@@ -1,5 +1,9 @@
using ApplicationLayer.Services.AuthServices.Common;
using System.ComponentModel.DataAnnotations;
using ApplicationLayer.Services.AuthServices.Common;
namespace ApplicationLayer.Services.AuthServices.Requests;
public record RegisterRequest(AuthData AuthData);
public class RegisterRequest
{
[Required] public AuthData AuthData { get; set; } = null!;
}

View File

@@ -1,5 +1,4 @@
using ApplicationLayer.Services.AuthServices.Common;
using ApplicationLayer.Services.AuthServices.NeededServices;
using Domains;
using FluentValidation;
@@ -7,25 +6,20 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation;
public class AuthDataValidator : AbstractValidator<AuthData>
{
public AuthDataValidator(IUsersRepository users)
public AuthDataValidator()
{
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.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}");
RuleFor(d => d.Password)
.NotEmpty()
.WithMessage("Password can not be empty")
.MaximumLength(ConfigurationConstraints.PasswordLength)
.WithMessage($"Password length must be less than {ConfigurationConstraints.PasswordLength}");
}
}
RuleFor(d => d.Password)
.NotEmpty()
.WithMessage("Password can not be empty")
.MaximumLength(ConfigurationConstraints.PasswordLength)
.WithMessage($"Password length must be less than {ConfigurationConstraints.PasswordLength}");
}
}

View File

@@ -1,27 +0,0 @@
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}");
}
}

View File

@@ -1,36 +0,0 @@
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");
}
}

View File

@@ -1,49 +0,0 @@
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}");
}
}

View File

@@ -1,8 +1,6 @@
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;
@@ -11,67 +9,67 @@ public class RegisterApplicantRequestValidator : AbstractValidator<RegisterAppli
{
public RegisterApplicantRequestValidator(
IDateTimeProvider dateTimeProvider,
IValidator<Name> nameValidator,
IValidator<AuthData> authDataValidator,
IValidator<Passport> passportValidator,
IValidator<NameModel> nameValidator,
IValidator<RegisterRequest> registerRequestValidator,
IValidator<PassportModel> passportValidator,
IValidator<PlaceOfWorkModel> placeOfWorkModelValidator)
{
RuleFor(r => r.AuthData)
.SetValidator(authDataValidator);
RuleFor(r => r.RegisterRequest)
.SetValidator(registerRequestValidator);
RuleFor(r => r.ApplicantName)
.SetValidator(nameValidator);
RuleFor(r => r.ApplicantName)
.SetValidator(nameValidator);
RuleFor(r => r.FatherName)
.SetValidator(nameValidator);
RuleFor(r => r.FatherName)
.SetValidator(nameValidator);
RuleFor(r => r.MotherName)
.SetValidator(nameValidator);
RuleFor(r => r.MotherName)
.SetValidator(nameValidator);
RuleFor(r => r.Passport)
.SetValidator(passportValidator);
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.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.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.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.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.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.Gender).IsInEnum();
RuleFor(r => r.MaritalStatus).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.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);
}
}
RuleFor(r => r.PlaceOfWork)
.SetValidator(placeOfWorkModelValidator);
}
}

View File

@@ -0,0 +1,16 @@
using ApplicationLayer.Services.AuthServices.Common;
using ApplicationLayer.Services.AuthServices.NeededServices;
using FluentValidation;
namespace ApplicationLayer.Services.AuthServices.Requests.Validation;
public class RegisterRequestValidator : AbstractValidator<RegisterRequest>
{
public RegisterRequestValidator(IUsersRepository users, IValidator<AuthData> authDataValidator)
{
RuleFor(r => r.AuthData)
.SetValidator(authDataValidator)
.MustAsync(async (authData, ct) => await users.FindByEmailAsync(authData.Email, ct) is null)
.WithMessage("Email already exists");
}
}

View File

@@ -0,0 +1,6 @@
using ApplicationLayer.Services.GeneralExceptions;
using Domains.Users;
namespace ApplicationLayer.Services.Users.Exceptions;
public class WrongRoleException(Guid userId) : EntityNotFoundByIdException<User>(userId);

View File

@@ -10,13 +10,13 @@ public interface IUsersService
/// <param name="cancellationToken">Cancellation token</param>
Task<List<User>> GetAuthoritiesAccountsAsync(CancellationToken cancellationToken);
/// Changes authentication data for an account
/// Changes authentication data for an authority account
/// <param name="request"> Request object with identifier of user and new authentication data</param>
/// <param name="cancellationToken">Cancellation token</param>
Task ChangeAccountAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken);
Task ChangeAuthorityAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken);
/// Removes user account
/// Removes account of authority
/// <param name="userId">Identifier of account</param>
/// <param name="cancellationToken">Cancellation token</param>
Task RemoveUserAccount(Guid userId, CancellationToken cancellationToken);
}
Task RemoveAuthorityAccount(Guid userId, CancellationToken cancellationToken);
}

View File

@@ -1,5 +1,11 @@
using ApplicationLayer.Services.AuthServices.Common;
using System.ComponentModel.DataAnnotations;
using ApplicationLayer.Services.AuthServices.Common;
namespace ApplicationLayer.Services.Users.Requests;
public record ChangeUserAuthDataRequest(Guid UserId, AuthData NewAuthData);
public class ChangeUserAuthDataRequest(Guid userId, AuthData newAuthData)
{
[Required] public Guid UserId { get; set; } = userId;
[Required] public AuthData NewAuthData { get; set; } = newAuthData;
}

View File

@@ -1,5 +1,7 @@
using ApplicationLayer.InfrastructureServicesInterfaces;
using ApplicationLayer.Services.AuthServices.Common;
using ApplicationLayer.Services.AuthServices.NeededServices;
using ApplicationLayer.Services.Users.Exceptions;
using ApplicationLayer.Services.Users.Requests;
using Domains.Users;
@@ -7,27 +9,59 @@ namespace ApplicationLayer.Services.Users;
public class UsersService(IUsersRepository users, IUnitOfWork unitOfWork) : IUsersService
{
async Task<List<User>> IUsersService.GetAuthoritiesAccountsAsync(CancellationToken cancellationToken)
async Task<List<User>> IUsersService.GetAuthoritiesAccountsAsync(CancellationToken cancellationToken) =>
await users.GetAllOfRoleAsync(Role.ApprovingAuthority, cancellationToken);
async Task IUsersService.ChangeAuthorityAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken)
{
return await users.GetAllOfRoleAsync(Role.ApprovingAuthority, cancellationToken);
}
var user = await users.GetByIdAsync(request.UserId, cancellationToken);
async Task IUsersService.ChangeAccountAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken)
ValidateRole(user, Role.ApprovingAuthority);
await ChangeAccountAuthDataAsync(user, request.NewAuthData, cancellationToken);
}
async Task IUsersService.RemoveAuthorityAccount(Guid userId, CancellationToken cancellationToken)
{
var user = await users.GetByIdAsync(request.UserId, cancellationToken);
var user = await users.GetByIdAsync(userId, cancellationToken);
user.Email = request.NewAuthData.Email;
user.Password = request.NewAuthData.Password;
await users.UpdateAsync(user, cancellationToken);
ValidateRole(user, Role.ApprovingAuthority);
await unitOfWork.SaveAsync(cancellationToken);
}
await RemoveUserAccount(user, cancellationToken);
}
async Task IUsersService.RemoveUserAccount(Guid userId, CancellationToken cancellationToken)
/// Updates user account auth data
/// <param name="user">User to remove</param>
/// <param name="authData">New auth data</param>
/// <param name="cancellationToken">Cancellation token</param>
private async Task ChangeAccountAuthDataAsync(User user, AuthData authData, CancellationToken cancellationToken)
{
var user = await users.GetByIdAsync(userId, cancellationToken);
users.Remove(user);
user.Email = authData.Email;
user.Password = authData.Password;
await users.UpdateAsync(user, cancellationToken);
await unitOfWork.SaveAsync(cancellationToken);
await unitOfWork.SaveAsync(cancellationToken);
}
/// Removes user account from DB
/// <param name="user">User to remove</param>
/// <param name="cancellationToken">Cancellation token</param>
private async Task RemoveUserAccount(User user, CancellationToken cancellationToken)
{
users.Remove(user);
await unitOfWork.SaveAsync(cancellationToken);
}
/// Checks if role of user equals expected
/// <param name="user">User to check</param>
/// <param name="expectedRole">Expected role</param>
/// <exception cref="WrongRoleException">Role is not expected</exception>
private static void ValidateRole(User user, Role expectedRole)
{
if (user.Role != expectedRole)
{
throw new WrongRoleException(user.Id);
}
}
}
}

View File

@@ -29,18 +29,6 @@ public class VisaApplicationRequestsHandler(
return applicationModels;
}
private async Task<VisaApplicationModelForAuthority> MapVisaApplicationToModelForAuthorities(VisaApplication visaApplication,
CancellationToken cancellationToken)
{
var applicant = await applicants.GetByIdAsync(visaApplication.ApplicantId, cancellationToken);
var applicantModel = mapper.Map<ApplicantModel>(applicant);
var model = mapper.Map<VisaApplicationModelForAuthority>(visaApplication);
model.Applicant = applicantModel;
return model;
}
public async Task<List<VisaApplicationModelForApplicant>> GetForApplicantAsync(CancellationToken cancellationToken)
{
var applicantId = await applicants.GetApplicantIdByUserId(userIdProvider.GetUserId(), cancellationToken);
@@ -83,7 +71,7 @@ public class VisaApplicationRequestsHandler(
throw new ApplicationAlreadyProcessedException();
}
ApplicationStatus statusToSet = status switch
var statusToSet = status switch
{
AuthorityRequestStatuses.Approved => ApplicationStatus.Approved,
AuthorityRequestStatuses.Rejected => ApplicationStatus.Rejected,
@@ -95,4 +83,16 @@ public class VisaApplicationRequestsHandler(
await unitOfWork.SaveAsync(cancellationToken);
}
}
private async Task<VisaApplicationModelForAuthority> MapVisaApplicationToModelForAuthorities(VisaApplication visaApplication,
CancellationToken cancellationToken)
{
var applicant = await applicants.GetByIdAsync(visaApplication.ApplicantId, cancellationToken);
var applicantModel = mapper.Map<ApplicantModel>(applicant);
var model = mapper.Map<VisaApplicationModelForAuthority>(visaApplication);
model.Applicant = applicantModel;
return model;
}
}

View File

@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Domains;
namespace ApplicationLayer.Services.VisaApplications.Models;
/// Model of past visa for presentation layer
public class PastVisaModel
{
// Date of issue
[Required]
public DateTime IssueDate { get; set; }
/// Name of visa
[Required]
[MaxLength(ConfigurationConstraints.VisaNameLength)]
public string Name { get; set; } = null!;
/// Date when visa expires
[Required]
public DateTime ExpirationDate { get; set; }
}

View File

@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using Domains;
namespace ApplicationLayer.Services.VisaApplications.Models;
/// Model of past visit for presentation layer
public class PastVisitModel
{
/// First day of past visit
[Required]
public DateTime StartDate { get; set; }
/// Last day of past visit
[Required]
public DateTime EndDate { get; set; }
/// Destination country of past visit
[Required]
[MaxLength(ConfigurationConstraints.CountryNameLength)]
public string DestinationCountry { get; set; } = null!;
}

View File

@@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
using Domains;
namespace ApplicationLayer.Services.VisaApplications.Models;
/// Model of permission to destination country for presentation layer
public class PermissionToDestCountryModel
{
/// Date when permission to destination country expires
[Required]
public DateTime ExpirationDate { get; set; }
/// Issuing authority
[Required]
[MaxLength(ConfigurationConstraints.IssuerNameLength)]
public string Issuer { get; set; } = null!;
}

View File

@@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
using Domains;
namespace ApplicationLayer.Services.VisaApplications.Models;
/// Model of re-entry permit for presentation layer
public class ReentryPermitModel
{
/// Number of re-entry permit
[Required]
[MaxLength(ConfigurationConstraints.ReentryPermitNumberLength)]
public string Number { get; set; } = null!;
/// Date when re-entry permit expires
[Required]
public DateTime ExpirationDate { get; set; }
}

View File

@@ -0,0 +1,26 @@
using ApplicationLayer.InfrastructureServicesInterfaces;
using FluentValidation;
namespace ApplicationLayer.Services.VisaApplications.Models.Validation;
public class PastVisaModelValidator : AbstractValidator<PastVisaModel>
{
public PastVisaModelValidator(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");
}
}

View File

@@ -0,0 +1,29 @@
using ApplicationLayer.InfrastructureServicesInterfaces;
using Domains;
using FluentValidation;
namespace ApplicationLayer.Services.VisaApplications.Models.Validation;
public class PastVisitModelValidator : AbstractValidator<PastVisitModel>
{
public PastVisitModelValidator(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}");
}
}

View File

@@ -0,0 +1,23 @@
using ApplicationLayer.InfrastructureServicesInterfaces;
using Domains;
using FluentValidation;
namespace ApplicationLayer.Services.VisaApplications.Models.Validation;
public class PermissionToDestCountryModelValidator : AbstractValidator<PermissionToDestCountryModel?>
{
public PermissionToDestCountryModelValidator(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}");
}
}

View File

@@ -0,0 +1,23 @@
using ApplicationLayer.InfrastructureServicesInterfaces;
using Domains;
using FluentValidation;
namespace ApplicationLayer.Services.VisaApplications.Models.Validation;
public class ReentryPermitModelValidator : AbstractValidator<ReentryPermitModel?>
{
public ReentryPermitModelValidator(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");
}
}

View File

@@ -2,42 +2,42 @@
namespace ApplicationLayer.Services.VisaApplications.Models;
/// Model of <see cref="VisaApplication"/>
/// Model of <see cref="VisaApplication" />
public class VisaApplicationModelForApplicant
{
/// <inheritdoc cref="VisaApplication.Id"/>
/// <inheritdoc cref="VisaApplication.Id" />
public Guid Id { get; set; }
/// <inheritdoc cref="VisaApplication.Status"/>
/// <inheritdoc cref="VisaApplication.Status" />
public ApplicationStatus Status { get; set; }
/// <inheritdoc cref="VisaApplication.ReentryPermit"/>
public ReentryPermit? ReentryPermit { get; set; }
/// <inheritdoc cref="VisaApplication.ReentryPermit" />
public ReentryPermitModel? ReentryPermit { get; set; }
/// <inheritdoc cref="VisaApplication.DestinationCountry"/>
/// <inheritdoc cref="VisaApplication.DestinationCountry" />
public string DestinationCountry { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PastVisas"/>
public List<PastVisa> PastVisas { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PastVisas" />
public List<PastVisaModel> PastVisas { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PermissionToDestCountry"/>
public PermissionToDestCountry? PermissionToDestCountry { get; set; }
/// <inheritdoc cref="VisaApplication.PermissionToDestCountry" />
public PermissionToDestCountryModel? PermissionToDestCountry { get; set; }
/// <inheritdoc cref="VisaApplication.PastVisits"/>
public List<PastVisit> PastVisits { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PastVisits" />
public List<PastVisitModel> PastVisits { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.VisaCategory"/>
/// <inheritdoc cref="VisaApplication.VisaCategory" />
public VisaCategory VisaCategory { get; set; }
/// <inheritdoc cref="VisaApplication.ForGroup"/>
/// <inheritdoc cref="VisaApplication.ForGroup" />
public bool ForGroup { get; set; }
/// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries"/>
/// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries" />
public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; }
/// <inheritdoc cref="VisaApplication.RequestDate"/>
/// <inheritdoc cref="VisaApplication.RequestDate" />
public DateTime RequestDate { get; set; }
/// <inheritdoc cref="VisaApplication.ValidDaysRequested"/>
/// <inheritdoc cref="VisaApplication.ValidDaysRequested" />
public int ValidDaysRequested { get; set; }
}
}

View File

@@ -3,44 +3,44 @@ using Domains.VisaApplicationDomain;
namespace ApplicationLayer.Services.VisaApplications.Models;
/// Model of <see cref="VisaApplication"/> with applicant property
/// Model of <see cref="VisaApplication" /> with applicant property
public class VisaApplicationModelForAuthority
{
/// <inheritdoc cref="VisaApplication.Id"/>
/// <inheritdoc cref="VisaApplication.Id" />
public Guid Id { get; set; }
/// Applicant of application
public ApplicantModel Applicant { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.Status"/>
/// <inheritdoc cref="VisaApplication.Status" />
public ApplicationStatus Status { get; set; }
/// <inheritdoc cref="VisaApplication.ReentryPermit"/>
public ReentryPermit? ReentryPermit { get; set; }
/// <inheritdoc cref="VisaApplication.ReentryPermit" />
public ReentryPermitModel? ReentryPermit { get; set; }
/// <inheritdoc cref="VisaApplication.DestinationCountry"/>
/// <inheritdoc cref="VisaApplication.DestinationCountry" />
public string DestinationCountry { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PastVisas"/>
public List<PastVisa> PastVisas { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PastVisas" />
public List<PastVisaModel> PastVisas { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PermissionToDestCountry"/>
public PermissionToDestCountry? PermissionToDestCountry { get; set; }
/// <inheritdoc cref="VisaApplication.PermissionToDestCountry" />
public PermissionToDestCountryModel? PermissionToDestCountry { get; set; }
public List<PastVisit> PastVisits { get; set; } = null!;
public List<PastVisitModel> PastVisits { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.VisaCategory"/>
/// <inheritdoc cref="VisaApplication.VisaCategory" />
public VisaCategory VisaCategory { get; set; }
/// <inheritdoc cref="VisaApplication.ForGroup"/>
/// <inheritdoc cref="VisaApplication.ForGroup" />
public bool ForGroup { get; set; }
/// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries"/>
/// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries" />
public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; }
/// <inheritdoc cref="VisaApplication.RequestDate"/>
/// <inheritdoc cref="VisaApplication.RequestDate" />
public DateTime RequestDate { get; set; }
/// <inheritdoc cref="VisaApplication.ValidDaysRequested"/>
/// <inheritdoc cref="VisaApplication.ValidDaysRequested" />
public int ValidDaysRequested { get; set; }
}
}

View File

@@ -1,27 +0,0 @@
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");
}
}

View File

@@ -1,30 +0,0 @@
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}");
}
}

View File

@@ -1,24 +0,0 @@
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}");
}
}

View File

@@ -1,24 +0,0 @@
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");
}
}

View File

@@ -1,5 +1,6 @@
using ApplicationLayer.InfrastructureServicesInterfaces;
using ApplicationLayer.Services.Applicants.NeededServices;
using ApplicationLayer.Services.VisaApplications.Models;
using Domains;
using Domains.VisaApplicationDomain;
using FluentValidation;
@@ -9,17 +10,17 @@ 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,
IValidator<ReentryPermitModel?> reentryPermitModelValidator,
IValidator<PastVisaModel> pastVisaModelValidator,
IValidator<PermissionToDestCountryModel?> permissionToDestCountryModelValidator,
IValidator<PastVisitModel> pastVisitModelValidator,
IApplicantsRepository applicants,
IUserIdProvider userIdProvider)
{
RuleFor(r => r.ReentryPermit)
.NotEmpty()
.WithMessage("Non-residents must provide re-entry permission")
.SetValidator(reentryPermitValidator)
.SetValidator(reentryPermitModelValidator)
.WhenAsync(async (_, ct) =>
await applicants.IsApplicantNonResidentByUserId(userIdProvider.GetUserId(), ct));
@@ -40,14 +41,14 @@ public class VisaApplicationCreateRequestValidator : AbstractValidator<VisaAppli
.WithMessage($"Valid days requested must be less than or equal to {ConfigurationConstraints.MaxValidDays}");
RuleForEach(r => r.PastVisas)
.SetValidator(pastVisaValidator);
.SetValidator(pastVisaModelValidator);
When(r => r.VisaCategory == VisaCategory.Transit,
() =>
RuleFor(r => r.PermissionToDestCountry)
.SetValidator(permissionToDestCountryValidator));
.SetValidator(permissionToDestCountryModelValidator));
RuleForEach(r => r.PastVisits)
.SetValidator(pastVisitValidator);
.SetValidator(pastVisitModelValidator);
}
}

View File

@@ -1,16 +1,40 @@
using Domains.VisaApplicationDomain;
using System.ComponentModel.DataAnnotations;
using ApplicationLayer.Services.VisaApplications.Models;
using Domains;
using Domains.VisaApplicationDomain;
namespace ApplicationLayer.Services.VisaApplications.Requests;
/// Model of visa request from user
public record VisaApplicationCreateRequest(
ReentryPermit? ReentryPermit,
string DestinationCountry,
VisaCategory VisaCategory,
bool IsForGroup,
RequestedNumberOfEntries RequestedNumberOfEntries,
int ValidDaysRequested,
PastVisa[] PastVisas,
PermissionToDestCountry? PermissionToDestCountry,
PastVisit[] PastVisits
);
public class VisaApplicationCreateRequest
{
[Required]
public ReentryPermitModel? ReentryPermit { get; set; }
[Required]
[MaxLength(ConfigurationConstraints.CountryNameLength)]
public string DestinationCountry { get; set; } = null!;
[Required]
public VisaCategory VisaCategory { get; set; }
[Required]
public bool IsForGroup { get; set; }
[Required]
public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; }
[Required]
[Range(0, ConfigurationConstraints.MaxValidDays)]
public int ValidDaysRequested { get; set; }
[Required]
public PastVisaModel[] PastVisas { get; set; } = null!;
[Required]
public PermissionToDestCountryModel? PermissionToDestCountry { get; set; }
[Required]
public PastVisitModel[] PastVisits { get; set; } = null!;
}