diff --git a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/AddressModel.cs b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/AddressModel.cs new file mode 100644 index 0000000..142b084 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/AddressModel.cs @@ -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!; +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/ApplicantModel.cs b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/ApplicantModel.cs index 1530e2e..b8cb159 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/ApplicantModel.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/ApplicantModel.cs @@ -2,48 +2,49 @@ namespace ApplicationLayer.Services.Applicants.Models; -/// Model of +/// Model of +/// public class ApplicantModel { - /// + /// public Name Name { get; set; } = null!; - /// + /// public Passport Passport { get; set; } = null!; - /// + /// public DateTime BirthDate { get; set; } - /// + /// public string CountryOfBirth { get; set; } = null!; - /// + /// public string CityOfBirth { get; set; } = null!; - /// + /// public string Citizenship { get; set; } = null!; - /// + /// public string CitizenshipByBirth { get; set; } = null!; - /// + /// public Gender Gender { get; set; } - /// + /// public MaritalStatus MaritalStatus { get; set; } - /// + /// public Name FatherName { get; set; } = null!; - /// + /// public Name MotherName { get; set; } = null!; - /// + /// public string JobTitle { get; set; } = null!; - /// + /// public PlaceOfWork PlaceOfWork { get; set; } = null!; - /// + /// public bool IsNonResident { get; set; } -} \ No newline at end of file +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/NameModel.cs b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/NameModel.cs new file mode 100644 index 0000000..fc3ddd9 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/NameModel.cs @@ -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; } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/PassportModel.cs b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/PassportModel.cs new file mode 100644 index 0000000..df135d4 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/PassportModel.cs @@ -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; } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/PlaceOfWorkModel.cs b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/PlaceOfWorkModel.cs index f7df5fb..4ffe7cb 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/PlaceOfWorkModel.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/PlaceOfWorkModel.cs @@ -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!; } \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/NameModelValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/NameModelValidator.cs new file mode 100644 index 0000000..2ac282d --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/NameModelValidator.cs @@ -0,0 +1,26 @@ +using Domains; +using FluentValidation; + +namespace ApplicationLayer.Services.Applicants.Models.Validation; + +public class NameModelValidator : AbstractValidator +{ + 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}"); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/PassportModelValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/PassportModelValidator.cs new file mode 100644 index 0000000..0671e7e --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/PassportModelValidator.cs @@ -0,0 +1,35 @@ +using ApplicationLayer.InfrastructureServicesInterfaces; +using Domains; +using FluentValidation; + +namespace ApplicationLayer.Services.Applicants.Models.Validation; + +public class PassportModelValidator : AbstractValidator +{ + 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"); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/PlaceOfWorkModelValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/PlaceOfWorkModelValidator.cs new file mode 100644 index 0000000..4008831 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/Applicants/Models/Validation/PlaceOfWorkModelValidator.cs @@ -0,0 +1,50 @@ +using Domains; +using FluentValidation; + +namespace ApplicationLayer.Services.Applicants.Models.Validation; + +public class PlaceOfWorkModelValidator : AbstractValidator +{ + 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}"); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Common/AuthData.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Common/AuthData.cs index 0eebb89..901c180 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Common/AuthData.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Common/AuthData.cs @@ -1,3 +1,15 @@ -namespace ApplicationLayer.Services.AuthServices.Common; +using System.ComponentModel.DataAnnotations; +using Domains; -public record AuthData(string Email, string Password); \ No newline at end of file +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!; +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/DevelopmentLoginService.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/DevelopmentLoginService.cs index 0434a6f..6abead2 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/DevelopmentLoginService.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/DevelopmentLoginService.cs @@ -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 ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken) + async Task 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); } -} \ No newline at end of file + + 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); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/ILoginService.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/ILoginService.cs index bb4e2a3..cb6d347 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/ILoginService.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/ILoginService.cs @@ -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 /// JWT-token - Task LoginAsync(string email, string password, CancellationToken cancellationToken); -} \ No newline at end of file + Task LoginAsync(LoginRequest request, CancellationToken cancellationToken); +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/LoginService.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/LoginService.cs index 33e44cf..c32ba13 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/LoginService.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/LoginService/LoginService.cs @@ -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; -/// +/// public class LoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService { - async Task ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken) + async Task 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(); } -} \ No newline at end of file + + return tokenGenerator.CreateToken(user); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/RegisterService/RegisterService.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/RegisterService/RegisterService.cs index 57bb4ed..ce86b7e 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/RegisterService/RegisterService.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/RegisterService/RegisterService.cs @@ -17,7 +17,7 @@ public class RegisterService( { async Task IRegisterService.RegisterApplicant(RegisterApplicantRequest request, CancellationToken cancellationToken) { - var user = mapper.Map(request.AuthData); + var user = mapper.Map(request.RegisterRequest.AuthData); user.Role = Role.Applicant; var applicant = mapper.Map(request); diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/LoginRequest.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/LoginRequest.cs new file mode 100644 index 0000000..d2b6f77 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/LoginRequest.cs @@ -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!; +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/RegisterApplicantRequest.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/RegisterApplicantRequest.cs index a82435b..77d5b39 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/RegisterApplicantRequest.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/RegisterApplicantRequest.cs @@ -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); \ No newline at end of file +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; } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/RegisterRequest.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/RegisterRequest.cs index aaa3bd3..4b57bd9 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/RegisterRequest.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/RegisterRequest.cs @@ -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); \ No newline at end of file +public class RegisterRequest +{ + [Required] public AuthData AuthData { get; set; } = null!; +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/AuthDataValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/AuthDataValidator.cs index a6f43fa..b8e1c55 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/AuthDataValidator.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/AuthDataValidator.cs @@ -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 { - 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}"); - } -} \ No newline at end of file + RuleFor(d => d.Password) + .NotEmpty() + .WithMessage("Password can not be empty") + .MaximumLength(ConfigurationConstraints.PasswordLength) + .WithMessage($"Password length must be less than {ConfigurationConstraints.PasswordLength}"); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/NameValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/NameValidator.cs deleted file mode 100644 index 5393f61..0000000 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/NameValidator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Domains; -using Domains.ApplicantDomain; -using FluentValidation; - -namespace ApplicationLayer.Services.AuthServices.Requests.Validation; - -public class NameValidator : AbstractValidator -{ - 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}"); - } -} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/PassportValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/PassportValidator.cs deleted file mode 100644 index 26a535f..0000000 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/PassportValidator.cs +++ /dev/null @@ -1,36 +0,0 @@ -using ApplicationLayer.InfrastructureServicesInterfaces; -using Domains; -using Domains.ApplicantDomain; -using FluentValidation; - -namespace ApplicationLayer.Services.AuthServices.Requests.Validation; - -public class PassportValidator : AbstractValidator -{ - 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"); - } -} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/PlaceOfWorkModelValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/PlaceOfWorkModelValidator.cs deleted file mode 100644 index dc965d3..0000000 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/PlaceOfWorkModelValidator.cs +++ /dev/null @@ -1,49 +0,0 @@ -using ApplicationLayer.Services.Applicants.Models; -using Domains; -using FluentValidation; - -namespace ApplicationLayer.Services.AuthServices.Requests.Validation; - -public class PlaceOfWorkModelValidator : AbstractValidator -{ - 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}"); - } -} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/RegisterApplicantRequestValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/RegisterApplicantRequestValidator.cs index 4004820..0a8b159 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/RegisterApplicantRequestValidator.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/RegisterApplicantRequestValidator.cs @@ -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 nameValidator, - IValidator authDataValidator, - IValidator passportValidator, + IValidator nameValidator, + IValidator registerRequestValidator, + IValidator passportValidator, IValidator 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); - } -} \ No newline at end of file + RuleFor(r => r.PlaceOfWork) + .SetValidator(placeOfWorkModelValidator); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/RegisterRequestValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/RegisterRequestValidator.cs new file mode 100644 index 0000000..87f342f --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/AuthServices/Requests/Validation/RegisterRequestValidator.cs @@ -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 +{ + public RegisterRequestValidator(IUsersRepository users, IValidator authDataValidator) + { + RuleFor(r => r.AuthData) + .SetValidator(authDataValidator) + .MustAsync(async (authData, ct) => await users.FindByEmailAsync(authData.Email, ct) is null) + .WithMessage("Email already exists"); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/Users/Exceptions/WrongRoleException.cs b/SchengenVisaApi/ApplicationLayer/Services/Users/Exceptions/WrongRoleException.cs new file mode 100644 index 0000000..4cb0705 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/Users/Exceptions/WrongRoleException.cs @@ -0,0 +1,6 @@ +using ApplicationLayer.Services.GeneralExceptions; +using Domains.Users; + +namespace ApplicationLayer.Services.Users.Exceptions; + +public class WrongRoleException(Guid userId) : EntityNotFoundByIdException(userId); diff --git a/SchengenVisaApi/ApplicationLayer/Services/Users/IUsersService.cs b/SchengenVisaApi/ApplicationLayer/Services/Users/IUsersService.cs index 9ca7d8a..713af1c 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/Users/IUsersService.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/Users/IUsersService.cs @@ -10,13 +10,13 @@ public interface IUsersService /// Cancellation token Task> GetAuthoritiesAccountsAsync(CancellationToken cancellationToken); - /// Changes authentication data for an account + /// Changes authentication data for an authority account /// Request object with identifier of user and new authentication data /// Cancellation token - Task ChangeAccountAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken); + Task ChangeAuthorityAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken); - /// Removes user account + /// Removes account of authority /// Identifier of account /// Cancellation token - Task RemoveUserAccount(Guid userId, CancellationToken cancellationToken); -} \ No newline at end of file + Task RemoveAuthorityAccount(Guid userId, CancellationToken cancellationToken); +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/Users/Requests/ChangeUserAuthDataRequest.cs b/SchengenVisaApi/ApplicationLayer/Services/Users/Requests/ChangeUserAuthDataRequest.cs index 42619b4..383d797 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/Users/Requests/ChangeUserAuthDataRequest.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/Users/Requests/ChangeUserAuthDataRequest.cs @@ -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); \ No newline at end of file +public class ChangeUserAuthDataRequest(Guid userId, AuthData newAuthData) +{ + [Required] public Guid UserId { get; set; } = userId; + + [Required] public AuthData NewAuthData { get; set; } = newAuthData; +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/Users/UsersService.cs b/SchengenVisaApi/ApplicationLayer/Services/Users/UsersService.cs index 1228bcc..eaaeb13 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/Users/UsersService.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/Users/UsersService.cs @@ -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> IUsersService.GetAuthoritiesAccountsAsync(CancellationToken cancellationToken) + async Task> 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 + /// User to remove + /// New auth data + /// Cancellation token + 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 + /// User to remove + /// Cancellation token + private async Task RemoveUserAccount(User user, CancellationToken cancellationToken) + { + users.Remove(user); + + await unitOfWork.SaveAsync(cancellationToken); + } + + /// Checks if role of user equals expected + /// User to check + /// Expected role + /// Role is not expected + private static void ValidateRole(User user, Role expectedRole) + { + if (user.Role != expectedRole) + { + throw new WrongRoleException(user.Id); } -} \ No newline at end of file + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs index 4088eae..23d93b6 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Handlers/VisaApplicationRequestsHandler.cs @@ -29,18 +29,6 @@ public class VisaApplicationRequestsHandler( return applicationModels; } - private async Task MapVisaApplicationToModelForAuthorities(VisaApplication visaApplication, - CancellationToken cancellationToken) - { - var applicant = await applicants.GetByIdAsync(visaApplication.ApplicantId, cancellationToken); - var applicantModel = mapper.Map(applicant); - - var model = mapper.Map(visaApplication); - model.Applicant = applicantModel; - - return model; - } - public async Task> 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 MapVisaApplicationToModelForAuthorities(VisaApplication visaApplication, + CancellationToken cancellationToken) + { + var applicant = await applicants.GetByIdAsync(visaApplication.ApplicantId, cancellationToken); + var applicantModel = mapper.Map(applicant); + + var model = mapper.Map(visaApplication); + model.Applicant = applicantModel; + + return model; + } +} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/PastVisaModel.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/PastVisaModel.cs new file mode 100644 index 0000000..8092a52 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/PastVisaModel.cs @@ -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; } +} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/PastVisitModel.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/PastVisitModel.cs new file mode 100644 index 0000000..f4dd901 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/PastVisitModel.cs @@ -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!; +} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/PermissionToDestCountryModel.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/PermissionToDestCountryModel.cs new file mode 100644 index 0000000..d83c987 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/PermissionToDestCountryModel.cs @@ -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!; +} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/ReentryPermitModel.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/ReentryPermitModel.cs new file mode 100644 index 0000000..1d81969 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/ReentryPermitModel.cs @@ -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; } +} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/PastVisaModelValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/PastVisaModelValidator.cs new file mode 100644 index 0000000..db09e36 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/PastVisaModelValidator.cs @@ -0,0 +1,26 @@ +using ApplicationLayer.InfrastructureServicesInterfaces; +using FluentValidation; + +namespace ApplicationLayer.Services.VisaApplications.Models.Validation; + +public class PastVisaModelValidator : AbstractValidator +{ + 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"); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/PastVisitModelValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/PastVisitModelValidator.cs new file mode 100644 index 0000000..19b4da3 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/PastVisitModelValidator.cs @@ -0,0 +1,29 @@ +using ApplicationLayer.InfrastructureServicesInterfaces; +using Domains; +using FluentValidation; + +namespace ApplicationLayer.Services.VisaApplications.Models.Validation; + +public class PastVisitModelValidator : AbstractValidator +{ + 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}"); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/PermissionToDestCountryModelValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/PermissionToDestCountryModelValidator.cs new file mode 100644 index 0000000..be267f7 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/PermissionToDestCountryModelValidator.cs @@ -0,0 +1,23 @@ +using ApplicationLayer.InfrastructureServicesInterfaces; +using Domains; +using FluentValidation; + +namespace ApplicationLayer.Services.VisaApplications.Models.Validation; + +public class PermissionToDestCountryModelValidator : AbstractValidator +{ + 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}"); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/ReentryPermitModelValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/ReentryPermitModelValidator.cs new file mode 100644 index 0000000..4dfb291 --- /dev/null +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/Validation/ReentryPermitModelValidator.cs @@ -0,0 +1,23 @@ +using ApplicationLayer.InfrastructureServicesInterfaces; +using Domains; +using FluentValidation; + +namespace ApplicationLayer.Services.VisaApplications.Models.Validation; + +public class ReentryPermitModelValidator : AbstractValidator +{ + 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"); + } +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/VisaApplicationModelForApplicant.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/VisaApplicationModelForApplicant.cs index c47e7ff..d12499b 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/VisaApplicationModelForApplicant.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/VisaApplicationModelForApplicant.cs @@ -2,42 +2,42 @@ namespace ApplicationLayer.Services.VisaApplications.Models; -/// Model of +/// Model of public class VisaApplicationModelForApplicant { - /// + /// public Guid Id { get; set; } - /// + /// public ApplicationStatus Status { get; set; } - /// - public ReentryPermit? ReentryPermit { get; set; } + /// + public ReentryPermitModel? ReentryPermit { get; set; } - /// + /// public string DestinationCountry { get; set; } = null!; - /// - public List PastVisas { get; set; } = null!; + /// + public List PastVisas { get; set; } = null!; - /// - public PermissionToDestCountry? PermissionToDestCountry { get; set; } + /// + public PermissionToDestCountryModel? PermissionToDestCountry { get; set; } - /// - public List PastVisits { get; set; } = null!; + /// + public List PastVisits { get; set; } = null!; - /// + /// public VisaCategory VisaCategory { get; set; } - /// + /// public bool ForGroup { get; set; } - /// + /// public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; } - /// + /// public DateTime RequestDate { get; set; } - /// + /// public int ValidDaysRequested { get; set; } -} \ No newline at end of file +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/VisaApplicationModelForAuthority.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/VisaApplicationModelForAuthority.cs index 2cbe42f..6142168 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/VisaApplicationModelForAuthority.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Models/VisaApplicationModelForAuthority.cs @@ -3,44 +3,44 @@ using Domains.VisaApplicationDomain; namespace ApplicationLayer.Services.VisaApplications.Models; -/// Model of with applicant property +/// Model of with applicant property public class VisaApplicationModelForAuthority { - /// + /// public Guid Id { get; set; } /// Applicant of application public ApplicantModel Applicant { get; set; } = null!; - /// + /// public ApplicationStatus Status { get; set; } - /// - public ReentryPermit? ReentryPermit { get; set; } + /// + public ReentryPermitModel? ReentryPermit { get; set; } - /// + /// public string DestinationCountry { get; set; } = null!; - /// - public List PastVisas { get; set; } = null!; + /// + public List PastVisas { get; set; } = null!; - /// - public PermissionToDestCountry? PermissionToDestCountry { get; set; } + /// + public PermissionToDestCountryModel? PermissionToDestCountry { get; set; } - public List PastVisits { get; set; } = null!; + public List PastVisits { get; set; } = null!; - /// + /// public VisaCategory VisaCategory { get; set; } - /// + /// public bool ForGroup { get; set; } - /// + /// public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; } - /// + /// public DateTime RequestDate { get; set; } - /// + /// public int ValidDaysRequested { get; set; } -} \ No newline at end of file +} diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/PastVisaValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/PastVisaValidator.cs deleted file mode 100644 index c0d9f80..0000000 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/PastVisaValidator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using ApplicationLayer.InfrastructureServicesInterfaces; -using Domains.VisaApplicationDomain; -using FluentValidation; - -namespace ApplicationLayer.Services.VisaApplications.Requests.Validation; - -public class PastVisaValidator : AbstractValidator -{ - 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"); - } -} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/PastVisitValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/PastVisitValidator.cs deleted file mode 100644 index 6799028..0000000 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/PastVisitValidator.cs +++ /dev/null @@ -1,30 +0,0 @@ -using ApplicationLayer.InfrastructureServicesInterfaces; -using Domains; -using Domains.VisaApplicationDomain; -using FluentValidation; - -namespace ApplicationLayer.Services.VisaApplications.Requests.Validation; - -public class PastVisitValidator : AbstractValidator -{ - 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}"); - } -} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/PermissionToDestCountryValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/PermissionToDestCountryValidator.cs deleted file mode 100644 index 5404cc6..0000000 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/PermissionToDestCountryValidator.cs +++ /dev/null @@ -1,24 +0,0 @@ -using ApplicationLayer.InfrastructureServicesInterfaces; -using Domains; -using Domains.VisaApplicationDomain; -using FluentValidation; - -namespace ApplicationLayer.Services.VisaApplications.Requests.Validation; - -public class PermissionToDestCountryValidator : AbstractValidator -{ - 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}"); - } -} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/ReentryPermitValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/ReentryPermitValidator.cs deleted file mode 100644 index 1b1cde1..0000000 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/ReentryPermitValidator.cs +++ /dev/null @@ -1,24 +0,0 @@ -using ApplicationLayer.InfrastructureServicesInterfaces; -using Domains; -using Domains.VisaApplicationDomain; -using FluentValidation; - -namespace ApplicationLayer.Services.VisaApplications.Requests.Validation; - -public class ReentryPermitValidator : AbstractValidator -{ - 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"); - } -} \ No newline at end of file diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/VisaApplicationCreateRequestValidator.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/VisaApplicationCreateRequestValidator.cs index 9298a6e..84e8bc6 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/VisaApplicationCreateRequestValidator.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/Validation/VisaApplicationCreateRequestValidator.cs @@ -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 { public VisaApplicationCreateRequestValidator( - IValidator reentryPermitValidator, - IValidator pastVisaValidator, - IValidator permissionToDestCountryValidator, - IValidator pastVisitValidator, + IValidator reentryPermitModelValidator, + IValidator pastVisaModelValidator, + IValidator permissionToDestCountryModelValidator, + IValidator 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 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); } } diff --git a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/VisaApplicationCreateRequest.cs b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/VisaApplicationCreateRequest.cs index caf5207..c9f29c1 100644 --- a/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/VisaApplicationCreateRequest.cs +++ b/SchengenVisaApi/ApplicationLayer/Services/VisaApplications/Requests/VisaApplicationCreateRequest.cs @@ -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!; +} diff --git a/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs b/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs index fc10d47..2a82f07 100644 --- a/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs +++ b/SchengenVisaApi/SchengenVisaApi/Controllers/UsersController.cs @@ -56,9 +56,9 @@ public class UsersController( [HttpGet("login")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task Login(string email, string password, CancellationToken cancellationToken) + public async Task Login(LoginRequest request, CancellationToken cancellationToken) { - var result = await loginService.LoginAsync(email, password, cancellationToken); + var result = await loginService.LoginAsync(request, cancellationToken); return Ok(result); } @@ -88,7 +88,7 @@ public class UsersController( { await authDataValidator.ValidateAndThrowAsync(authData, cancellationToken); - await usersService.ChangeAccountAuthDataAsync(new ChangeUserAuthDataRequest(authorityAccountId, authData), cancellationToken); + await usersService.ChangeAuthorityAuthDataAsync(new ChangeUserAuthDataRequest(authorityAccountId, authData), cancellationToken); return Ok(); } @@ -102,7 +102,7 @@ public class UsersController( [Authorize(policy: PolicyConstants.AdminPolicy)] public async Task RemoveAuthorityAccount(Guid authorityAccountId, CancellationToken cancellationToken) { - await usersService.RemoveUserAccount(authorityAccountId, cancellationToken); + await usersService.RemoveAuthorityAccount(authorityAccountId, cancellationToken); return Ok(); } } diff --git a/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs b/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs index b415f16..64eb5e8 100644 --- a/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs +++ b/SchengenVisaApi/SchengenVisaApi/Controllers/VisaApplicationController.cs @@ -82,6 +82,9 @@ public class VisaApplicationController( [ProducesResponseType(StatusCodes.Status404NotFound)] [Authorize(policy: PolicyConstants.ApprovingAuthorityPolicy)] public async Task SetStatusFromAuthority(Guid applicationId, AuthorityRequestStatuses status, CancellationToken cancellationToken) + public async Task SetStatusFromAuthority(Guid applicationId, + AuthorityRequestStatuses status, + CancellationToken cancellationToken) { await visaApplicationRequestsHandler.SetApplicationStatusFromAuthorityAsync(applicationId, status, cancellationToken); return Ok();