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; namespace ApplicationLayer.Services.Applicants.Models;
/// Model of <see cref="Applicant"/> /// Model of
/// <see cref="Applicant" />
public class ApplicantModel public class ApplicantModel
{ {
/// <inheritdoc cref="Applicant.Name"/> /// <inheritdoc cref="Applicant.Name" />
public Name Name { get; set; } = null!; public Name Name { get; set; } = null!;
/// <inheritdoc cref="Applicant.Passport"/> /// <inheritdoc cref="Applicant.Passport" />
public Passport Passport { get; set; } = null!; public Passport Passport { get; set; } = null!;
/// <inheritdoc cref="Applicant.BirthDate"/> /// <inheritdoc cref="Applicant.BirthDate" />
public DateTime BirthDate { get; set; } public DateTime BirthDate { get; set; }
/// <inheritdoc cref="Applicant.CountryOfBirth"/> /// <inheritdoc cref="Applicant.CountryOfBirth" />
public string CountryOfBirth { get; set; } = null!; public string CountryOfBirth { get; set; } = null!;
/// <inheritdoc cref="Applicant.CityOfBirth"/> /// <inheritdoc cref="Applicant.CityOfBirth" />
public string CityOfBirth { get; set; } = null!; public string CityOfBirth { get; set; } = null!;
/// <inheritdoc cref="Applicant.Citizenship"/> /// <inheritdoc cref="Applicant.Citizenship" />
public string Citizenship { get; set; } = null!; public string Citizenship { get; set; } = null!;
/// <inheritdoc cref="Applicant.CitizenshipByBirth"/> /// <inheritdoc cref="Applicant.CitizenshipByBirth" />
public string CitizenshipByBirth { get; set; } = null!; public string CitizenshipByBirth { get; set; } = null!;
/// <inheritdoc cref="Applicant.Gender"/> /// <inheritdoc cref="Applicant.Gender" />
public Gender Gender { get; set; } public Gender Gender { get; set; }
/// <inheritdoc cref="Applicant.MaritalStatus"/> /// <inheritdoc cref="Applicant.MaritalStatus" />
public MaritalStatus MaritalStatus { get; set; } public MaritalStatus MaritalStatus { get; set; }
/// <inheritdoc cref="Applicant.FatherName"/> /// <inheritdoc cref="Applicant.FatherName" />
public Name FatherName { get; set; } = null!; public Name FatherName { get; set; } = null!;
/// <inheritdoc cref="Applicant.MotherName"/> /// <inheritdoc cref="Applicant.MotherName" />
public Name MotherName { get; set; } = null!; public Name MotherName { get; set; } = null!;
/// <inheritdoc cref="Applicant.JobTitle"/> /// <inheritdoc cref="Applicant.JobTitle" />
public string JobTitle { get; set; } = null!; public string JobTitle { get; set; } = null!;
/// <inheritdoc cref="Applicant.PlaceOfWork"/> /// <inheritdoc cref="Applicant.PlaceOfWork" />
public PlaceOfWork PlaceOfWork { get; set; } = null!; public PlaceOfWork PlaceOfWork { get; set; } = null!;
/// <inheritdoc cref="Applicant.IsNonResident"/> /// <inheritdoc cref="Applicant.IsNonResident" />
public bool IsNonResident { get; set; } 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; namespace ApplicationLayer.Services.Applicants.Models;
public class PlaceOfWorkModel public class PlaceOfWorkModel
{ {
/// Name of hirer /// Name of hirer
[Required]
[MaxLength(ConfigurationConstraints.PlaceOfWorkNameLength)]
public string Name { get; set; } = null!; public string Name { get; set; } = null!;
/// Address of hirer /// Address of hirer
public Address Address { get; set; } = null!; [Required]
public AddressModel Address { get; set; } = null!;
/// Phone number of hirer /// Phone number of hirer
[Required]
[MaxLength(ConfigurationConstraints.PhoneNumberLength)]
[MinLength(ConfigurationConstraints.PhoneNumberMinLength)]
public string PhoneNum { get; set; } = null!; 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,22 +1,23 @@
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(string email, string password, CancellationToken cancellationToken) async Task<string> ILoginService.LoginAsync(LoginRequest request, CancellationToken cancellationToken)
{ {
if (email == "admin@mail.ru" && password == "admin") if (request.AuthData is { 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(email, cancellationToken); var user = await users.FindByEmailAsync(request.AuthData.Email, cancellationToken);
if (user is null || user.Password != password) if (user is null || user.Password != request.AuthData.Password)
{ {
throw new IncorrectLoginDataException(); throw new IncorrectLoginDataException();
} }

View File

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

View File

@@ -1,15 +1,16 @@
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(string email, string password, CancellationToken cancellationToken) async Task<string> ILoginService.LoginAsync(LoginRequest request, CancellationToken cancellationToken)
{ {
var user = await users.FindByEmailAsync(email, cancellationToken); var user = await users.FindByEmailAsync(request.AuthData.Email, cancellationToken);
if (user is null || user.Password != password) if (user is null || user.Password != request.AuthData.Password)
{ {
throw new IncorrectLoginDataException(); throw new IncorrectLoginDataException();
} }

View File

@@ -17,7 +17,7 @@ public class RegisterService(
{ {
async Task IRegisterService.RegisterApplicant(RegisterApplicantRequest request, CancellationToken cancellationToken) 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; user.Role = Role.Applicant;
var applicant = mapper.Map<Applicant>(request); 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 System.ComponentModel.DataAnnotations;
using ApplicationLayer.Services.AuthServices.Common; using ApplicationLayer.Services.Applicants.Models;
using Domains;
using Domains.ApplicantDomain; using Domains.ApplicantDomain;
namespace ApplicationLayer.Services.AuthServices.Requests; namespace ApplicationLayer.Services.AuthServices.Requests;
public record RegisterApplicantRequest( public record RegisterApplicantRequest
AuthData AuthData, {
Name ApplicantName, [Required] public RegisterRequest RegisterRequest { get; set; } = null!;
Passport Passport,
DateTime BirthDate, [Required] public NameModel ApplicantName { get; set; } = null!;
string CityOfBirth,
string CountryOfBirth, [Required] public PassportModel Passport { get; set; } = null!;
string Citizenship,
string CitizenshipByBirth, [Required] public DateTime BirthDate { get; set; }
Gender Gender,
MaritalStatus MaritalStatus, [Required]
Name FatherName, [MaxLength(ConfigurationConstraints.CityNameLength)]
Name MotherName, public string CityOfBirth { get; set; } = null!;
string JobTitle,
PlaceOfWorkModel PlaceOfWork, [Required]
bool IsNonResident) : RegisterRequest(AuthData); [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; 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.Common;
using ApplicationLayer.Services.AuthServices.NeededServices;
using Domains; using Domains;
using FluentValidation; using FluentValidation;
@@ -7,7 +6,7 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation;
public class AuthDataValidator : AbstractValidator<AuthData> public class AuthDataValidator : AbstractValidator<AuthData>
{ {
public AuthDataValidator(IUsersRepository users) public AuthDataValidator()
{ {
RuleFor(d => d.Email) RuleFor(d => d.Email)
.NotEmpty() .NotEmpty()
@@ -15,12 +14,7 @@ public class AuthDataValidator : AbstractValidator<AuthData>
.EmailAddress() .EmailAddress()
.WithMessage("Email must be valid") .WithMessage("Email must be valid")
.MaximumLength(ConfigurationConstraints.EmailLength) .MaximumLength(ConfigurationConstraints.EmailLength)
.WithMessage($"Email length must be less than {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) RuleFor(d => d.Password)
.NotEmpty() .NotEmpty()

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.InfrastructureServicesInterfaces;
using ApplicationLayer.Services.Applicants.Models; using ApplicationLayer.Services.Applicants.Models;
using ApplicationLayer.Services.AuthServices.Common;
using Domains; using Domains;
using Domains.ApplicantDomain;
using FluentValidation; using FluentValidation;
namespace ApplicationLayer.Services.AuthServices.Requests.Validation; namespace ApplicationLayer.Services.AuthServices.Requests.Validation;
@@ -11,13 +9,13 @@ public class RegisterApplicantRequestValidator : AbstractValidator<RegisterAppli
{ {
public RegisterApplicantRequestValidator( public RegisterApplicantRequestValidator(
IDateTimeProvider dateTimeProvider, IDateTimeProvider dateTimeProvider,
IValidator<Name> nameValidator, IValidator<NameModel> nameValidator,
IValidator<AuthData> authDataValidator, IValidator<RegisterRequest> registerRequestValidator,
IValidator<Passport> passportValidator, IValidator<PassportModel> passportValidator,
IValidator<PlaceOfWorkModel> placeOfWorkModelValidator) IValidator<PlaceOfWorkModel> placeOfWorkModelValidator)
{ {
RuleFor(r => r.AuthData) RuleFor(r => r.RegisterRequest)
.SetValidator(authDataValidator); .SetValidator(registerRequestValidator);
RuleFor(r => r.ApplicantName) RuleFor(r => r.ApplicantName)
.SetValidator(nameValidator); .SetValidator(nameValidator);

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> /// <param name="cancellationToken">Cancellation token</param>
Task<List<User>> GetAuthoritiesAccountsAsync(CancellationToken cancellationToken); 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="request"> Request object with identifier of user and new authentication data</param>
/// <param name="cancellationToken">Cancellation token</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="userId">Identifier of account</param>
/// <param name="cancellationToken">Cancellation token</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; 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.InfrastructureServicesInterfaces;
using ApplicationLayer.Services.AuthServices.Common;
using ApplicationLayer.Services.AuthServices.NeededServices; using ApplicationLayer.Services.AuthServices.NeededServices;
using ApplicationLayer.Services.Users.Exceptions;
using ApplicationLayer.Services.Users.Requests; using ApplicationLayer.Services.Users.Requests;
using Domains.Users; using Domains.Users;
@@ -7,27 +9,59 @@ namespace ApplicationLayer.Services.Users;
public class UsersService(IUsersRepository users, IUnitOfWork unitOfWork) : IUsersService 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);
return await users.GetAllOfRoleAsync(Role.ApprovingAuthority, cancellationToken);
}
async Task IUsersService.ChangeAccountAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken) async Task IUsersService.ChangeAuthorityAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken)
{ {
var user = await users.GetByIdAsync(request.UserId, cancellationToken); var user = await users.GetByIdAsync(request.UserId, cancellationToken);
user.Email = request.NewAuthData.Email; ValidateRole(user, Role.ApprovingAuthority);
user.Password = request.NewAuthData.Password;
await ChangeAccountAuthDataAsync(user, request.NewAuthData, cancellationToken);
}
async Task IUsersService.RemoveAuthorityAccount(Guid userId, CancellationToken cancellationToken)
{
var user = await users.GetByIdAsync(userId, cancellationToken);
ValidateRole(user, Role.ApprovingAuthority);
await RemoveUserAccount(user, 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)
{
user.Email = authData.Email;
user.Password = authData.Password;
await users.UpdateAsync(user, cancellationToken); await users.UpdateAsync(user, cancellationToken);
await unitOfWork.SaveAsync(cancellationToken); await unitOfWork.SaveAsync(cancellationToken);
} }
async Task IUsersService.RemoveUserAccount(Guid userId, CancellationToken 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)
{ {
var user = await users.GetByIdAsync(userId, cancellationToken);
users.Remove(user); users.Remove(user);
await unitOfWork.SaveAsync(cancellationToken); 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; 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) public async Task<List<VisaApplicationModelForApplicant>> GetForApplicantAsync(CancellationToken cancellationToken)
{ {
var applicantId = await applicants.GetApplicantIdByUserId(userIdProvider.GetUserId(), cancellationToken); var applicantId = await applicants.GetApplicantIdByUserId(userIdProvider.GetUserId(), cancellationToken);
@@ -83,7 +71,7 @@ public class VisaApplicationRequestsHandler(
throw new ApplicationAlreadyProcessedException(); throw new ApplicationAlreadyProcessedException();
} }
ApplicationStatus statusToSet = status switch var statusToSet = status switch
{ {
AuthorityRequestStatuses.Approved => ApplicationStatus.Approved, AuthorityRequestStatuses.Approved => ApplicationStatus.Approved,
AuthorityRequestStatuses.Rejected => ApplicationStatus.Rejected, AuthorityRequestStatuses.Rejected => ApplicationStatus.Rejected,
@@ -95,4 +83,16 @@ public class VisaApplicationRequestsHandler(
await unitOfWork.SaveAsync(cancellationToken); 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; namespace ApplicationLayer.Services.VisaApplications.Models;
/// Model of <see cref="VisaApplication"/> /// Model of <see cref="VisaApplication" />
public class VisaApplicationModelForApplicant public class VisaApplicationModelForApplicant
{ {
/// <inheritdoc cref="VisaApplication.Id"/> /// <inheritdoc cref="VisaApplication.Id" />
public Guid Id { get; set; } public Guid Id { get; set; }
/// <inheritdoc cref="VisaApplication.Status"/> /// <inheritdoc cref="VisaApplication.Status" />
public ApplicationStatus Status { get; set; } public ApplicationStatus Status { get; set; }
/// <inheritdoc cref="VisaApplication.ReentryPermit"/> /// <inheritdoc cref="VisaApplication.ReentryPermit" />
public ReentryPermit? ReentryPermit { get; set; } public ReentryPermitModel? ReentryPermit { get; set; }
/// <inheritdoc cref="VisaApplication.DestinationCountry"/> /// <inheritdoc cref="VisaApplication.DestinationCountry" />
public string DestinationCountry { get; set; } = null!; public string DestinationCountry { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PastVisas"/> /// <inheritdoc cref="VisaApplication.PastVisas" />
public List<PastVisa> PastVisas { get; set; } = null!; public List<PastVisaModel> PastVisas { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PermissionToDestCountry"/> /// <inheritdoc cref="VisaApplication.PermissionToDestCountry" />
public PermissionToDestCountry? PermissionToDestCountry { get; set; } public PermissionToDestCountryModel? PermissionToDestCountry { get; set; }
/// <inheritdoc cref="VisaApplication.PastVisits"/> /// <inheritdoc cref="VisaApplication.PastVisits" />
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; } public VisaCategory VisaCategory { get; set; }
/// <inheritdoc cref="VisaApplication.ForGroup"/> /// <inheritdoc cref="VisaApplication.ForGroup" />
public bool ForGroup { get; set; } public bool ForGroup { get; set; }
/// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries"/> /// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries" />
public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; } public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; }
/// <inheritdoc cref="VisaApplication.RequestDate"/> /// <inheritdoc cref="VisaApplication.RequestDate" />
public DateTime RequestDate { get; set; } public DateTime RequestDate { get; set; }
/// <inheritdoc cref="VisaApplication.ValidDaysRequested"/> /// <inheritdoc cref="VisaApplication.ValidDaysRequested" />
public int ValidDaysRequested { get; set; } public int ValidDaysRequested { get; set; }
} }

View File

@@ -3,44 +3,44 @@ using Domains.VisaApplicationDomain;
namespace ApplicationLayer.Services.VisaApplications.Models; namespace ApplicationLayer.Services.VisaApplications.Models;
/// Model of <see cref="VisaApplication"/> with applicant property /// Model of <see cref="VisaApplication" /> with applicant property
public class VisaApplicationModelForAuthority public class VisaApplicationModelForAuthority
{ {
/// <inheritdoc cref="VisaApplication.Id"/> /// <inheritdoc cref="VisaApplication.Id" />
public Guid Id { get; set; } public Guid Id { get; set; }
/// Applicant of application /// Applicant of application
public ApplicantModel Applicant { get; set; } = null!; public ApplicantModel Applicant { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.Status"/> /// <inheritdoc cref="VisaApplication.Status" />
public ApplicationStatus Status { get; set; } public ApplicationStatus Status { get; set; }
/// <inheritdoc cref="VisaApplication.ReentryPermit"/> /// <inheritdoc cref="VisaApplication.ReentryPermit" />
public ReentryPermit? ReentryPermit { get; set; } public ReentryPermitModel? ReentryPermit { get; set; }
/// <inheritdoc cref="VisaApplication.DestinationCountry"/> /// <inheritdoc cref="VisaApplication.DestinationCountry" />
public string DestinationCountry { get; set; } = null!; public string DestinationCountry { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PastVisas"/> /// <inheritdoc cref="VisaApplication.PastVisas" />
public List<PastVisa> PastVisas { get; set; } = null!; public List<PastVisaModel> PastVisas { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PermissionToDestCountry"/> /// <inheritdoc cref="VisaApplication.PermissionToDestCountry" />
public PermissionToDestCountry? PermissionToDestCountry { get; set; } 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; } public VisaCategory VisaCategory { get; set; }
/// <inheritdoc cref="VisaApplication.ForGroup"/> /// <inheritdoc cref="VisaApplication.ForGroup" />
public bool ForGroup { get; set; } public bool ForGroup { get; set; }
/// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries"/> /// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries" />
public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; } public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; }
/// <inheritdoc cref="VisaApplication.RequestDate"/> /// <inheritdoc cref="VisaApplication.RequestDate" />
public DateTime RequestDate { get; set; } public DateTime RequestDate { get; set; }
/// <inheritdoc cref="VisaApplication.ValidDaysRequested"/> /// <inheritdoc cref="VisaApplication.ValidDaysRequested" />
public int ValidDaysRequested { get; set; } 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.InfrastructureServicesInterfaces;
using ApplicationLayer.Services.Applicants.NeededServices; using ApplicationLayer.Services.Applicants.NeededServices;
using ApplicationLayer.Services.VisaApplications.Models;
using Domains; using Domains;
using Domains.VisaApplicationDomain; using Domains.VisaApplicationDomain;
using FluentValidation; using FluentValidation;
@@ -9,17 +10,17 @@ namespace ApplicationLayer.Services.VisaApplications.Requests.Validation;
public class VisaApplicationCreateRequestValidator : AbstractValidator<VisaApplicationCreateRequest> public class VisaApplicationCreateRequestValidator : AbstractValidator<VisaApplicationCreateRequest>
{ {
public VisaApplicationCreateRequestValidator( public VisaApplicationCreateRequestValidator(
IValidator<ReentryPermit?> reentryPermitValidator, IValidator<ReentryPermitModel?> reentryPermitModelValidator,
IValidator<PastVisa> pastVisaValidator, IValidator<PastVisaModel> pastVisaModelValidator,
IValidator<PermissionToDestCountry?> permissionToDestCountryValidator, IValidator<PermissionToDestCountryModel?> permissionToDestCountryModelValidator,
IValidator<PastVisit> pastVisitValidator, IValidator<PastVisitModel> pastVisitModelValidator,
IApplicantsRepository applicants, IApplicantsRepository applicants,
IUserIdProvider userIdProvider) IUserIdProvider userIdProvider)
{ {
RuleFor(r => r.ReentryPermit) RuleFor(r => r.ReentryPermit)
.NotEmpty() .NotEmpty()
.WithMessage("Non-residents must provide re-entry permission") .WithMessage("Non-residents must provide re-entry permission")
.SetValidator(reentryPermitValidator) .SetValidator(reentryPermitModelValidator)
.WhenAsync(async (_, ct) => .WhenAsync(async (_, ct) =>
await applicants.IsApplicantNonResidentByUserId(userIdProvider.GetUserId(), 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}"); .WithMessage($"Valid days requested must be less than or equal to {ConfigurationConstraints.MaxValidDays}");
RuleForEach(r => r.PastVisas) RuleForEach(r => r.PastVisas)
.SetValidator(pastVisaValidator); .SetValidator(pastVisaModelValidator);
When(r => r.VisaCategory == VisaCategory.Transit, When(r => r.VisaCategory == VisaCategory.Transit,
() => () =>
RuleFor(r => r.PermissionToDestCountry) RuleFor(r => r.PermissionToDestCountry)
.SetValidator(permissionToDestCountryValidator)); .SetValidator(permissionToDestCountryModelValidator));
RuleForEach(r => r.PastVisits) 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; namespace ApplicationLayer.Services.VisaApplications.Requests;
/// Model of visa request from user /// Model of visa request from user
public record VisaApplicationCreateRequest( public class VisaApplicationCreateRequest
ReentryPermit? ReentryPermit, {
string DestinationCountry,
VisaCategory VisaCategory, [Required]
bool IsForGroup, public ReentryPermitModel? ReentryPermit { get; set; }
RequestedNumberOfEntries RequestedNumberOfEntries,
int ValidDaysRequested, [Required]
PastVisa[] PastVisas, [MaxLength(ConfigurationConstraints.CountryNameLength)]
PermissionToDestCountry? PermissionToDestCountry, public string DestinationCountry { get; set; } = null!;
PastVisit[] PastVisits
); [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!;
}

View File

@@ -56,9 +56,9 @@ public class UsersController(
[HttpGet("login")] [HttpGet("login")]
[ProducesResponseType<string>(StatusCodes.Status200OK)] [ProducesResponseType<string>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> Login(string email, string password, CancellationToken cancellationToken) public async Task<IActionResult> Login(LoginRequest request, CancellationToken cancellationToken)
{ {
var result = await loginService.LoginAsync(email, password, cancellationToken); var result = await loginService.LoginAsync(request, cancellationToken);
return Ok(result); return Ok(result);
} }
@@ -88,7 +88,7 @@ public class UsersController(
{ {
await authDataValidator.ValidateAndThrowAsync(authData, cancellationToken); await authDataValidator.ValidateAndThrowAsync(authData, cancellationToken);
await usersService.ChangeAccountAuthDataAsync(new ChangeUserAuthDataRequest(authorityAccountId, authData), cancellationToken); await usersService.ChangeAuthorityAuthDataAsync(new ChangeUserAuthDataRequest(authorityAccountId, authData), cancellationToken);
return Ok(); return Ok();
} }
@@ -102,7 +102,7 @@ public class UsersController(
[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 usersService.RemoveUserAccount(authorityAccountId, cancellationToken); await usersService.RemoveAuthorityAccount(authorityAccountId, cancellationToken);
return Ok(); return Ok();
} }
} }

View File

@@ -82,6 +82,9 @@ public class VisaApplicationController(
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(policy: PolicyConstants.ApprovingAuthorityPolicy)] [Authorize(policy: PolicyConstants.ApprovingAuthorityPolicy)]
public async Task<IActionResult> SetStatusFromAuthority(Guid applicationId, AuthorityRequestStatuses status, CancellationToken cancellationToken) public async Task<IActionResult> SetStatusFromAuthority(Guid applicationId, AuthorityRequestStatuses status, CancellationToken cancellationToken)
public async Task<IActionResult> SetStatusFromAuthority(Guid applicationId,
AuthorityRequestStatuses status,
CancellationToken cancellationToken)
{ {
await visaApplicationRequestsHandler.SetApplicationStatusFromAuthorityAsync(applicationId, status, cancellationToken); await visaApplicationRequestsHandler.SetApplicationStatusFromAuthorityAsync(applicationId, status, cancellationToken);
return Ok(); return Ok();