13
SchengenVisaApi/ApplicationLayer/Constants.cs
Normal file
13
SchengenVisaApi/ApplicationLayer/Constants.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ApplicationLayer
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public readonly static Regex EnglishWordRegex = new("^[a-zA-Z]*$");
|
||||
|
||||
public readonly static Regex EnglishPhraseRegex = new(@"^[a-zA-Z№0-9?><;,{}[\]\-_+=!@#$%\^&*|']*$");
|
||||
|
||||
public readonly static Regex PhoneNumRegex = new(@"^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Domains;
|
||||
|
||||
namespace ApplicationLayer.Services.GeneralExceptions;
|
||||
namespace ApplicationLayer.GeneralExceptions;
|
||||
|
||||
/// Exception to throw when entity not found
|
||||
/// <param name="id">Identifier of entity</param>
|
||||
@@ -1,6 +1,4 @@
|
||||
using ApplicationLayer.GeneralExceptions;
|
||||
|
||||
namespace ApplicationLayer.Services.GeneralExceptions;
|
||||
namespace ApplicationLayer.GeneralExceptions;
|
||||
|
||||
/// Exception to throw when entity not found
|
||||
public class EntityNotFoundException(string message) : ApiException(message);
|
||||
@@ -0,0 +1,11 @@
|
||||
using ApplicationLayer.Services.AuthServices.Common;
|
||||
using Domains.Users;
|
||||
|
||||
namespace ApplicationLayer.InfrastructureServicesInterfaces;
|
||||
|
||||
/// Generates jwt-tokens
|
||||
public interface ITokenGenerator
|
||||
{
|
||||
/// returns jwt-token for specific user
|
||||
AuthToken CreateToken(User user);
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
@@ -1,49 +1,64 @@
|
||||
using Domains.ApplicantDomain;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Domains.ApplicantDomain;
|
||||
|
||||
namespace ApplicationLayer.Services.Applicants.Models;
|
||||
|
||||
/// Model of <see cref="Applicant"/>
|
||||
/// Model of <see cref="Applicant" />
|
||||
public class ApplicantModel
|
||||
{
|
||||
/// <inheritdoc cref="Applicant.Name"/>
|
||||
public Name Name { get; set; } = null!;
|
||||
/// <inheritdoc cref="Applicant.Name" />
|
||||
[Required]
|
||||
public NameModel Name { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="Applicant.Passport"/>
|
||||
public Passport Passport { get; set; } = null!;
|
||||
/// <inheritdoc cref="Applicant.Passport" />
|
||||
[Required]
|
||||
public PassportModel Passport { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="Applicant.BirthDate"/>
|
||||
/// <inheritdoc cref="Applicant.BirthDate" />
|
||||
[Required]
|
||||
public DateTime BirthDate { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Applicant.CountryOfBirth"/>
|
||||
/// <inheritdoc cref="Applicant.CountryOfBirth" />
|
||||
[Required]
|
||||
public string CountryOfBirth { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="Applicant.CityOfBirth"/>
|
||||
/// <inheritdoc cref="Applicant.CityOfBirth" />
|
||||
[Required]
|
||||
public string CityOfBirth { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="Applicant.Citizenship"/>
|
||||
/// <inheritdoc cref="Applicant.Citizenship" />
|
||||
[Required]
|
||||
public string Citizenship { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="Applicant.CitizenshipByBirth"/>
|
||||
/// <inheritdoc cref="Applicant.CitizenshipByBirth" />
|
||||
[Required]
|
||||
public string CitizenshipByBirth { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="Applicant.Gender"/>
|
||||
/// <inheritdoc cref="Applicant.Gender" />
|
||||
[Required]
|
||||
public Gender Gender { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Applicant.MaritalStatus"/>
|
||||
/// <inheritdoc cref="Applicant.MaritalStatus" />
|
||||
[Required]
|
||||
public MaritalStatus MaritalStatus { get; set; }
|
||||
|
||||
/// <inheritdoc cref="Applicant.FatherName"/>
|
||||
public Name FatherName { get; set; } = null!;
|
||||
/// <inheritdoc cref="Applicant.FatherName" />
|
||||
[Required]
|
||||
public NameModel FatherName { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="Applicant.MotherName"/>
|
||||
public Name MotherName { get; set; } = null!;
|
||||
/// <inheritdoc cref="Applicant.MotherName" />
|
||||
[Required]
|
||||
public NameModel MotherName { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="Applicant.JobTitle"/>
|
||||
/// <inheritdoc cref="Applicant.JobTitle" />
|
||||
[Required]
|
||||
public string JobTitle { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="Applicant.PlaceOfWork"/>
|
||||
public PlaceOfWork PlaceOfWork { get; set; } = null!;
|
||||
/// <inheritdoc cref="Applicant.PlaceOfWork" />
|
||||
[Required]
|
||||
public PlaceOfWorkModel PlaceOfWork { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="Applicant.IsNonResident"/>
|
||||
/// <inheritdoc cref="Applicant.IsNonResident" />
|
||||
[Required]
|
||||
public bool IsNonResident { get; set; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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")
|
||||
.Matches(Constants.EnglishWordRegex)
|
||||
.WithMessage("First name must be in english characters")
|
||||
.MaximumLength(ConfigurationConstraints.NameLength)
|
||||
.WithMessage($"First Name length must be less than {ConfigurationConstraints.NameLength}");
|
||||
|
||||
RuleFor(m => m.Surname)
|
||||
.NotEmpty()
|
||||
.WithMessage("Surname can not be empty")
|
||||
.Matches(Constants.EnglishWordRegex)
|
||||
.WithMessage("Surname must be in english characters")
|
||||
.MaximumLength(ConfigurationConstraints.NameLength)
|
||||
.WithMessage($"Surname length must be less than {ConfigurationConstraints.NameLength}");
|
||||
|
||||
RuleFor(m => m.Patronymic)
|
||||
.Matches(Constants.EnglishWordRegex)
|
||||
.WithMessage("Patronymic must be in english characters")
|
||||
.MaximumLength(ConfigurationConstraints.NameLength)
|
||||
.WithMessage($"Patronymic length must be less than {ConfigurationConstraints.NameLength}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Passport issuer field can contain only english letters, digits and special symbols")
|
||||
.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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Passport number field can contain only english letters, digits and special symbols")
|
||||
.MaximumLength(ConfigurationConstraints.PassportNumberLength)
|
||||
.WithMessage($"Passport number length must be less than {ConfigurationConstraints.PassportNumberLength}");
|
||||
|
||||
RuleFor(r => r.ExpirationDate)
|
||||
.NotEmpty()
|
||||
.WithMessage("Passport expiration date can not be empty")
|
||||
.GreaterThan(dateTimeProvider.Now())
|
||||
.WithMessage("Can not approve visa for applicants with expired passport");
|
||||
|
||||
RuleFor(r => r.IssueDate)
|
||||
.NotEmpty()
|
||||
.WithMessage("Passport issue date can not be empty")
|
||||
.LessThanOrEqualTo(dateTimeProvider.Now())
|
||||
.WithMessage("Passport issue date must be in past");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Place of work name field can contain only english letters, digits and special symbols")
|
||||
.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")
|
||||
.Matches(Constants.PhoneNumRegex)
|
||||
.WithMessage("Place of work phone number field must be valid")
|
||||
.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)
|
||||
.NotEmpty()
|
||||
.WithMessage("Place of work address can not be empty");
|
||||
|
||||
RuleFor(p => p.Address.Country)
|
||||
.NotEmpty()
|
||||
.WithMessage("Country name of place of work can not be empty")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Place of work Country field can contain only english letters, digits and special symbols")
|
||||
.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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Place of work City field can contain only english letters, digits and special symbols")
|
||||
.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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Place of work Street field can contain only english letters, digits and special symbols")
|
||||
.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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Place of work building field can contain only english letters, digits and special symbols")
|
||||
.MaximumLength(ConfigurationConstraints.CountryNameLength)
|
||||
.WithMessage($"Building of place of work length must be less than {ConfigurationConstraints.BuildingNumberLength}");
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ApplicationLayer.Services.AuthServices.Common
|
||||
{
|
||||
public class AuthToken
|
||||
{
|
||||
[Required] public string Token { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
using ApplicationLayer.Services.AuthServices.LoginService.Exceptions;
|
||||
using ApplicationLayer.InfrastructureServicesInterfaces;
|
||||
using ApplicationLayer.Services.AuthServices.Common;
|
||||
using ApplicationLayer.Services.AuthServices.LoginService.Exceptions;
|
||||
using ApplicationLayer.Services.AuthServices.NeededServices;
|
||||
using ApplicationLayer.Services.AuthServices.Requests;
|
||||
using Domains.Users;
|
||||
|
||||
namespace ApplicationLayer.Services.AuthServices.LoginService;
|
||||
|
||||
public class DevelopmentLoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService
|
||||
{
|
||||
async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken)
|
||||
async Task<AuthToken> ILoginService.LoginAsync(LoginRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (email == "admin@mail.ru" && password == "admin")
|
||||
{
|
||||
var admin = new User { Role = Role.Admin };
|
||||
if (request.AuthData is { Email: "admin@mail.ru", Password: "admin" })
|
||||
{
|
||||
var admin = new User { Role = Role.Admin };
|
||||
|
||||
return tokenGenerator.CreateToken(admin);
|
||||
}
|
||||
|
||||
var user = await users.FindByEmailAsync(email, cancellationToken);
|
||||
if (user is null || user.Password != password)
|
||||
{
|
||||
throw new IncorrectLoginDataException();
|
||||
}
|
||||
|
||||
return tokenGenerator.CreateToken(user);
|
||||
return tokenGenerator.CreateToken(admin);
|
||||
}
|
||||
|
||||
var user = await users.FindByEmailAsync(request.AuthData.Email, cancellationToken);
|
||||
if (user is null || user.Password != request.AuthData.Password)
|
||||
{
|
||||
throw new IncorrectLoginDataException();
|
||||
}
|
||||
|
||||
return tokenGenerator.CreateToken(user);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
namespace ApplicationLayer.Services.AuthServices.LoginService;
|
||||
using ApplicationLayer.Services.AuthServices.Common;
|
||||
using ApplicationLayer.Services.AuthServices.Requests;
|
||||
|
||||
namespace ApplicationLayer.Services.AuthServices.LoginService;
|
||||
|
||||
/// Handles login requests
|
||||
public interface ILoginService
|
||||
{
|
||||
/// Handle login request
|
||||
/// <returns>JWT-token</returns>
|
||||
Task<string> LoginAsync(string email, string password, CancellationToken cancellationToken);
|
||||
Task<AuthToken> LoginAsync(LoginRequest request, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
using ApplicationLayer.Services.AuthServices.LoginService.Exceptions;
|
||||
using ApplicationLayer.InfrastructureServicesInterfaces;
|
||||
using ApplicationLayer.Services.AuthServices.Common;
|
||||
using ApplicationLayer.Services.AuthServices.LoginService.Exceptions;
|
||||
using ApplicationLayer.Services.AuthServices.NeededServices;
|
||||
using ApplicationLayer.Services.AuthServices.Requests;
|
||||
|
||||
namespace ApplicationLayer.Services.AuthServices.LoginService;
|
||||
|
||||
/// <inheritdoc cref="ILoginService"/>
|
||||
/// <inheritdoc cref="ILoginService" />
|
||||
public class LoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService
|
||||
{
|
||||
async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken)
|
||||
async Task<AuthToken> ILoginService.LoginAsync(LoginRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await users.FindByEmailAsync(email, cancellationToken);
|
||||
if (user is null || user.Password != password)
|
||||
{
|
||||
throw new IncorrectLoginDataException();
|
||||
}
|
||||
|
||||
return tokenGenerator.CreateToken(user);
|
||||
var user = await users.FindByEmailAsync(request.AuthData.Email, cancellationToken);
|
||||
if (user is null || user.Password != request.AuthData.Password)
|
||||
{
|
||||
throw new IncorrectLoginDataException();
|
||||
}
|
||||
|
||||
return tokenGenerator.CreateToken(user);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Domains.Users;
|
||||
|
||||
namespace ApplicationLayer.Services.AuthServices.NeededServices;
|
||||
|
||||
/// Generates jwt-tokens
|
||||
public interface ITokenGenerator
|
||||
{
|
||||
/// returns jwt-token for specific user
|
||||
string CreateToken(User user);
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public class RegisterService(
|
||||
{
|
||||
async Task IRegisterService.RegisterApplicant(RegisterApplicantRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = mapper.Map<User>(request.AuthData);
|
||||
var user = mapper.Map<User>(request.RegisterRequest.AuthData);
|
||||
user.Role = Role.Applicant;
|
||||
|
||||
var applicant = mapper.Map<Applicant>(request);
|
||||
|
||||
@@ -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!;
|
||||
}
|
||||
@@ -1,22 +1,49 @@
|
||||
using ApplicationLayer.Services.Applicants.Models;
|
||||
using ApplicationLayer.Services.AuthServices.Common;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using ApplicationLayer.Services.Applicants.Models;
|
||||
using Domains;
|
||||
using Domains.ApplicantDomain;
|
||||
|
||||
namespace ApplicationLayer.Services.AuthServices.Requests;
|
||||
|
||||
public record RegisterApplicantRequest(
|
||||
AuthData AuthData,
|
||||
Name ApplicantName,
|
||||
Passport Passport,
|
||||
DateTime BirthDate,
|
||||
string CityOfBirth,
|
||||
string CountryOfBirth,
|
||||
string Citizenship,
|
||||
string CitizenshipByBirth,
|
||||
Gender Gender,
|
||||
MaritalStatus MaritalStatus,
|
||||
Name FatherName,
|
||||
Name MotherName,
|
||||
string JobTitle,
|
||||
PlaceOfWorkModel PlaceOfWork,
|
||||
bool IsNonResident) : RegisterRequest(AuthData);
|
||||
public record RegisterApplicantRequest
|
||||
{
|
||||
[Required] public RegisterRequest RegisterRequest { get; set; } = null!;
|
||||
|
||||
[Required] public NameModel ApplicantName { get; set; } = null!;
|
||||
|
||||
[Required] public PassportModel Passport { get; set; } = null!;
|
||||
|
||||
[Required] public DateTime BirthDate { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(ConfigurationConstraints.CityNameLength)]
|
||||
public string CityOfBirth { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[MaxLength(ConfigurationConstraints.CountryNameLength)]
|
||||
public string CountryOfBirth { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[MaxLength(ConfigurationConstraints.CitizenshipLength)]
|
||||
public string Citizenship { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[MaxLength(ConfigurationConstraints.CitizenshipLength)]
|
||||
public string CitizenshipByBirth { get; set; } = null!;
|
||||
|
||||
[Required] public Gender Gender { get; set; }
|
||||
|
||||
[Required] public MaritalStatus MaritalStatus { get; set; }
|
||||
|
||||
[Required] public NameModel FatherName { get; set; } = null!;
|
||||
|
||||
[Required] public NameModel MotherName { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[MaxLength(ConfigurationConstraints.JobTitleLength)]
|
||||
public string JobTitle { get; set; } = null!;
|
||||
|
||||
[Required] public PlaceOfWorkModel PlaceOfWork { get; set; } = null!;
|
||||
|
||||
[Required] public bool IsNonResident { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using ApplicationLayer.Services.AuthServices.Common;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using ApplicationLayer.Services.AuthServices.Common;
|
||||
|
||||
namespace ApplicationLayer.Services.AuthServices.Requests;
|
||||
|
||||
public record RegisterRequest(AuthData AuthData);
|
||||
public class RegisterRequest
|
||||
{
|
||||
[Required] public AuthData AuthData { get; set; } = null!;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using ApplicationLayer.Services.AuthServices.Common;
|
||||
using ApplicationLayer.Services.AuthServices.NeededServices;
|
||||
using Domains;
|
||||
using FluentValidation;
|
||||
|
||||
@@ -7,25 +6,22 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation;
|
||||
|
||||
public class AuthDataValidator : AbstractValidator<AuthData>
|
||||
{
|
||||
public AuthDataValidator(IUsersRepository users)
|
||||
public AuthDataValidator()
|
||||
{
|
||||
RuleFor(d => d.Email)
|
||||
.NotEmpty()
|
||||
.WithMessage("Email can not be empty")
|
||||
.EmailAddress()
|
||||
.WithMessage("Email must be valid")
|
||||
.MaximumLength(ConfigurationConstraints.EmailLength)
|
||||
.WithMessage($"Email length must be less than {ConfigurationConstraints.EmailLength}")
|
||||
.MustAsync(async (email, ct) =>
|
||||
{
|
||||
return await users.FindByEmailAsync(email, ct) is null;
|
||||
})
|
||||
.WithMessage("Email already exists");
|
||||
RuleFor(d => d.Email)
|
||||
.NotEmpty()
|
||||
.WithMessage("Email can not be empty")
|
||||
.EmailAddress()
|
||||
.WithMessage("Email must be valid")
|
||||
.MaximumLength(ConfigurationConstraints.EmailLength)
|
||||
.WithMessage($"Email length must be less than {ConfigurationConstraints.EmailLength}");
|
||||
|
||||
RuleFor(d => d.Password)
|
||||
.NotEmpty()
|
||||
.WithMessage("Password can not be empty")
|
||||
.MaximumLength(ConfigurationConstraints.PasswordLength)
|
||||
.WithMessage($"Password length must be less than {ConfigurationConstraints.PasswordLength}");
|
||||
}
|
||||
RuleFor(d => d.Password)
|
||||
.NotEmpty()
|
||||
.WithMessage("Password can not be empty")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Password can contain only english letters, digits and special symbols")
|
||||
.MaximumLength(ConfigurationConstraints.PasswordLength)
|
||||
.WithMessage($"Password length must be less than {ConfigurationConstraints.PasswordLength}");
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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,85 @@ public class RegisterApplicantRequestValidator : AbstractValidator<RegisterAppli
|
||||
{
|
||||
public RegisterApplicantRequestValidator(
|
||||
IDateTimeProvider dateTimeProvider,
|
||||
IValidator<Name> nameValidator,
|
||||
IValidator<AuthData> authDataValidator,
|
||||
IValidator<Passport> passportValidator,
|
||||
IValidator<NameModel> nameValidator,
|
||||
IValidator<RegisterRequest> registerRequestValidator,
|
||||
IValidator<PassportModel> passportValidator,
|
||||
IValidator<PlaceOfWorkModel> placeOfWorkModelValidator)
|
||||
{
|
||||
RuleFor(r => r.AuthData)
|
||||
.SetValidator(authDataValidator);
|
||||
RuleFor(r => r.RegisterRequest)
|
||||
.NotEmpty()
|
||||
.SetValidator(registerRequestValidator);
|
||||
|
||||
RuleFor(r => r.ApplicantName)
|
||||
.SetValidator(nameValidator);
|
||||
RuleFor(r => r.ApplicantName)
|
||||
.NotEmpty()
|
||||
.SetValidator(nameValidator);
|
||||
|
||||
RuleFor(r => r.FatherName)
|
||||
.SetValidator(nameValidator);
|
||||
RuleFor(r => r.FatherName)
|
||||
.NotEmpty()
|
||||
.SetValidator(nameValidator);
|
||||
|
||||
RuleFor(r => r.MotherName)
|
||||
.SetValidator(nameValidator);
|
||||
RuleFor(r => r.MotherName)
|
||||
.NotEmpty()
|
||||
.SetValidator(nameValidator);
|
||||
|
||||
RuleFor(r => r.Passport)
|
||||
.SetValidator(passportValidator);
|
||||
RuleFor(r => r.Passport)
|
||||
.NotEmpty()
|
||||
.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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Country of birth field can contain only english letters, digits and special symbols")
|
||||
.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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("City of birth field can contain only english letters, digits and special symbols")
|
||||
.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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Citizenship field can contain only english letters, digits and special symbols")
|
||||
.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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Citizenship by birth field can contain only english letters, digits and special symbols")
|
||||
.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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Title of job field can contain only english letters, digits and special symbols")
|
||||
.MaximumLength(ConfigurationConstraints.JobTitleLength)
|
||||
.WithMessage($"Title of job length must be less than {ConfigurationConstraints.JobTitleLength}");
|
||||
|
||||
RuleFor(r => r.PlaceOfWork)
|
||||
.SetValidator(placeOfWorkModelValidator);
|
||||
}
|
||||
RuleFor(r => r.PlaceOfWork)
|
||||
.NotEmpty()
|
||||
.SetValidator(placeOfWorkModelValidator);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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)
|
||||
.NotEmpty()
|
||||
.SetValidator(authDataValidator)
|
||||
.MustAsync(async (authData, ct) => await users.FindByEmailAsync(authData.Email, ct) is null)
|
||||
.WithMessage("Email already exists");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using ApplicationLayer.GeneralExceptions;
|
||||
using Domains.Users;
|
||||
|
||||
namespace ApplicationLayer.Services.Users.Exceptions;
|
||||
|
||||
public class WrongRoleException(Guid userId) : EntityNotFoundByIdException<User>(userId);
|
||||
@@ -1,5 +1,6 @@
|
||||
using ApplicationLayer.Services.Users.Requests;
|
||||
using Domains.Users;
|
||||
using ApplicationLayer.Services.Applicants.Models;
|
||||
using ApplicationLayer.Services.Users.Models;
|
||||
using ApplicationLayer.Services.Users.Requests;
|
||||
|
||||
namespace ApplicationLayer.Services.Users;
|
||||
|
||||
@@ -8,15 +9,19 @@ public interface IUsersService
|
||||
{
|
||||
/// Returns all user accounts with role of approving authority
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
Task<List<User>> GetAuthoritiesAccountsAsync(CancellationToken cancellationToken);
|
||||
Task<List<UserModel>> GetAuthoritiesAccountsAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// Changes authentication data for an account
|
||||
/// Changes authentication data for an authority account
|
||||
/// <param name="request"> Request object with identifier of user and new authentication data</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
Task ChangeAccountAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken);
|
||||
Task ChangeAuthorityAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken);
|
||||
|
||||
/// Removes user account
|
||||
/// Removes account of authority
|
||||
/// <param name="userId">Identifier of account</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
Task RemoveUserAccount(Guid userId, CancellationToken cancellationToken);
|
||||
Task RemoveAuthorityAccount(Guid userId, CancellationToken cancellationToken);
|
||||
|
||||
/// Get applicant that made request
|
||||
/// <param name="cancellationToken">cancellation token</param>
|
||||
Task<ApplicantModel> GetAuthenticatedApplicant(CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Domains;
|
||||
|
||||
namespace ApplicationLayer.Services.Users.Models
|
||||
{
|
||||
/// Auth data with nullable password for making change auth data requests
|
||||
public class ChangeAuthData
|
||||
{
|
||||
[Required]
|
||||
[MaxLength(ConfigurationConstraints.EmailLength)]
|
||||
public string Email { get; set; } = null!;
|
||||
|
||||
[MaxLength(ConfigurationConstraints.PasswordLength)]
|
||||
public string? Password { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Domains;
|
||||
|
||||
namespace ApplicationLayer.Services.Users.Models
|
||||
{
|
||||
public class UserModel
|
||||
{
|
||||
/// Unique Identifier of user
|
||||
[Required]
|
||||
public Guid Id { get; private set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[MaxLength(ConfigurationConstraints.EmailLength)]
|
||||
public string Email { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
using ApplicationLayer.Services.AuthServices.Common;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using ApplicationLayer.Services.Users.Models;
|
||||
|
||||
namespace ApplicationLayer.Services.Users.Requests;
|
||||
|
||||
public record ChangeUserAuthDataRequest(Guid UserId, AuthData NewAuthData);
|
||||
public class ChangeUserAuthDataRequest(Guid userId, ChangeAuthData newAuthData)
|
||||
{
|
||||
[Required] public Guid UserId { get; set; } = userId;
|
||||
|
||||
[Required] public ChangeAuthData NewAuthData { get; set; } = newAuthData;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Domains;
|
||||
using FluentValidation;
|
||||
|
||||
namespace ApplicationLayer.Services.Users.Requests.Validation
|
||||
{
|
||||
public class ChangeUserAuthDataRequestValidator : AbstractValidator<ChangeUserAuthDataRequest>
|
||||
{
|
||||
public ChangeUserAuthDataRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.NewAuthData)
|
||||
.NotEmpty();
|
||||
|
||||
RuleFor(r => r.NewAuthData.Email)
|
||||
.NotEmpty()
|
||||
.EmailAddress()
|
||||
.WithMessage("Email should be valid")
|
||||
.MaximumLength(ConfigurationConstraints.EmailLength)
|
||||
.WithMessage($"Email address length must be less than {ConfigurationConstraints.EmailLength}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,85 @@
|
||||
using ApplicationLayer.InfrastructureServicesInterfaces;
|
||||
using ApplicationLayer.Services.Applicants.Models;
|
||||
using ApplicationLayer.Services.Applicants.NeededServices;
|
||||
using ApplicationLayer.Services.AuthServices.NeededServices;
|
||||
using ApplicationLayer.Services.Users.Exceptions;
|
||||
using ApplicationLayer.Services.Users.Models;
|
||||
using ApplicationLayer.Services.Users.Requests;
|
||||
using AutoMapper;
|
||||
using Domains.Users;
|
||||
|
||||
namespace ApplicationLayer.Services.Users;
|
||||
|
||||
public class UsersService(IUsersRepository users, IUnitOfWork unitOfWork) : IUsersService
|
||||
public class UsersService(
|
||||
IMapper mapper,
|
||||
IUserIdProvider userIdProvider,
|
||||
IUsersRepository users,
|
||||
IApplicantsRepository applicants,
|
||||
IUnitOfWork unitOfWork) : IUsersService
|
||||
{
|
||||
async Task<List<User>> IUsersService.GetAuthoritiesAccountsAsync(CancellationToken cancellationToken)
|
||||
async Task<List<UserModel>> IUsersService.GetAuthoritiesAccountsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return await users.GetAllOfRoleAsync(Role.ApprovingAuthority, cancellationToken);
|
||||
}
|
||||
var userList = await users.GetAllOfRoleAsync(Role.ApprovingAuthority, cancellationToken);
|
||||
return mapper.Map<List<UserModel>>(userList);
|
||||
}
|
||||
|
||||
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;
|
||||
user.Password = request.NewAuthData.Password;
|
||||
await users.UpdateAsync(user, cancellationToken);
|
||||
ValidateRole(user, Role.ApprovingAuthority);
|
||||
|
||||
await unitOfWork.SaveAsync(cancellationToken);
|
||||
}
|
||||
await ChangeAccountAuthDataAsync(user, request.NewAuthData, cancellationToken);
|
||||
}
|
||||
|
||||
async Task IUsersService.RemoveUserAccount(Guid userId, CancellationToken cancellationToken)
|
||||
async Task IUsersService.RemoveAuthorityAccount(Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = await users.GetByIdAsync(userId, cancellationToken);
|
||||
users.Remove(user);
|
||||
var user = await users.GetByIdAsync(userId, cancellationToken);
|
||||
|
||||
await unitOfWork.SaveAsync(cancellationToken);
|
||||
ValidateRole(user, Role.ApprovingAuthority);
|
||||
|
||||
await RemoveUserAccount(user, cancellationToken);
|
||||
}
|
||||
|
||||
async Task<ApplicantModel> IUsersService.GetAuthenticatedApplicant(CancellationToken cancellationToken)
|
||||
{
|
||||
var applicant = await applicants.FindByUserIdAsync(userIdProvider.GetUserId(), cancellationToken);
|
||||
|
||||
return mapper.Map<ApplicantModel>(applicant);
|
||||
}
|
||||
|
||||
/// 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, ChangeAuthData authData, CancellationToken cancellationToken)
|
||||
{
|
||||
user.Email = authData.Email;
|
||||
user.Password = authData.Password ?? user.Password;
|
||||
await users.UpdateAsync(user, cancellationToken);
|
||||
|
||||
await unitOfWork.SaveAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// Removes user account from DB
|
||||
/// <param name="user">User to remove</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
private async Task RemoveUserAccount(User user, CancellationToken cancellationToken)
|
||||
{
|
||||
users.Remove(user);
|
||||
|
||||
await unitOfWork.SaveAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// Checks if role of user equals expected
|
||||
/// <param name="user">User to check</param>
|
||||
/// <param name="expectedRole">Expected role</param>
|
||||
/// <exception cref="WrongRoleException">Role is not expected</exception>
|
||||
private static void ValidateRole(User user, Role expectedRole)
|
||||
{
|
||||
if (user.Role != expectedRole)
|
||||
{
|
||||
throw new WrongRoleException(user.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,16 @@ namespace ApplicationLayer.Services.VisaApplications.Handlers;
|
||||
public interface IVisaApplicationRequestsHandler
|
||||
{
|
||||
/// Returns all applications for approving authorities
|
||||
Task<List<VisaApplicationModelForAuthority>> GetPendingAsync(CancellationToken cancellationToken);
|
||||
Task<List<VisaApplicationPreview>> GetPendingAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// Returns all applications of one applicant
|
||||
Task<List<VisaApplicationModelForApplicant>> GetForApplicantAsync(CancellationToken cancellationToken);
|
||||
Task<List<VisaApplicationPreview>> GetForApplicantAsync(CancellationToken cancellationToken);
|
||||
|
||||
/// Returns one application with specific id
|
||||
Task<VisaApplicationModel> GetApplicationForApplicantAsync(Guid id, CancellationToken cancellationToken);
|
||||
|
||||
/// Returns one application with specific id
|
||||
Task<VisaApplicationModel> GetApplicationForAuthorityAsync(Guid id, CancellationToken cancellationToken);
|
||||
|
||||
/// Creates application for applicant with specific user identifier
|
||||
Task HandleCreateRequestAsync(VisaApplicationCreateRequest request, CancellationToken cancellationToken);
|
||||
|
||||
@@ -19,36 +19,41 @@ public class VisaApplicationRequestsHandler(
|
||||
IDateTimeProvider dateTimeProvider,
|
||||
IUserIdProvider userIdProvider) : IVisaApplicationRequestsHandler
|
||||
{
|
||||
async Task<List<VisaApplicationModelForAuthority>> IVisaApplicationRequestsHandler.GetPendingAsync(CancellationToken cancellationToken)
|
||||
async Task<List<VisaApplicationPreview>> IVisaApplicationRequestsHandler.GetPendingAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var applicationsList = await applications.GetPendingApplicationsAsync(cancellationToken);
|
||||
|
||||
var applicationModels = applicationsList
|
||||
.Select(a => MapVisaApplicationToModelForAuthorities(a, cancellationToken).Result)
|
||||
.ToList();
|
||||
var applicationModels = mapper.Map<List<VisaApplicationPreview>>(applicationsList);
|
||||
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)
|
||||
async Task<List<VisaApplicationPreview>> IVisaApplicationRequestsHandler.GetForApplicantAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var applicantId = await applicants.GetApplicantIdByUserId(userIdProvider.GetUserId(), cancellationToken);
|
||||
var visaApplications = await applications.GetOfApplicantAsync(applicantId, cancellationToken);
|
||||
return mapper.Map<List<VisaApplicationModelForApplicant>>(visaApplications);
|
||||
return mapper.Map<List<VisaApplicationPreview>>(visaApplications);
|
||||
}
|
||||
|
||||
public async Task HandleCreateRequestAsync(VisaApplicationCreateRequest request, CancellationToken cancellationToken)
|
||||
async Task<VisaApplicationModel> IVisaApplicationRequestsHandler.GetApplicationForApplicantAsync(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var applicant = await applicants.FindByUserIdAsync(userIdProvider.GetUserId(), cancellationToken);
|
||||
var application = await applications.GetByApplicantAndApplicationIdAsync(applicant.Id, id, cancellationToken);
|
||||
var mapped = mapper.Map<VisaApplicationModel>(application);
|
||||
mapped.Applicant = mapper.Map<ApplicantModel>(applicant);
|
||||
return mapped;
|
||||
}
|
||||
|
||||
async Task<VisaApplicationModel> IVisaApplicationRequestsHandler.GetApplicationForAuthorityAsync(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
var pending = await applications.GetPendingApplicationsAsync(cancellationToken);
|
||||
var application = pending.SingleOrDefault(a => a.Id == id) ?? throw new ApplicationAlreadyProcessedException();
|
||||
var mapped = mapper.Map<VisaApplicationModel>(application);
|
||||
var applicant = await applicants.GetByIdAsync(application.ApplicantId, cancellationToken);
|
||||
mapped.Applicant = mapper.Map<ApplicantModel>(applicant);
|
||||
return mapped;
|
||||
}
|
||||
|
||||
async Task IVisaApplicationRequestsHandler.HandleCreateRequestAsync(VisaApplicationCreateRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var applicant = await applicants.FindByUserIdAsync(userIdProvider.GetUserId(), cancellationToken);
|
||||
|
||||
@@ -65,6 +70,10 @@ public class VisaApplicationRequestsHandler(
|
||||
{
|
||||
var applicantId = await applicants.GetApplicantIdByUserId(userIdProvider.GetUserId(), cancellationToken);
|
||||
var application = await applications.GetByApplicantAndApplicationIdAsync(applicantId, applicationId, cancellationToken);
|
||||
if (application.Status is ApplicationStatus.Approved or ApplicationStatus.Rejected)
|
||||
{
|
||||
throw new ApplicationAlreadyProcessedException();
|
||||
}
|
||||
|
||||
application.Status = ApplicationStatus.Closed;
|
||||
await applications.UpdateAsync(application, cancellationToken);
|
||||
@@ -83,7 +92,7 @@ public class VisaApplicationRequestsHandler(
|
||||
throw new ApplicationAlreadyProcessedException();
|
||||
}
|
||||
|
||||
ApplicationStatus statusToSet = status switch
|
||||
var statusToSet = status switch
|
||||
{
|
||||
AuthorityRequestStatuses.Approved => ApplicationStatus.Approved,
|
||||
AuthorityRequestStatuses.Rejected => ApplicationStatus.Rejected,
|
||||
|
||||
@@ -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
|
||||
[MaxLength(ConfigurationConstraints.VisaNameLength)]
|
||||
[Required]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// Date when visa expires
|
||||
[Required]
|
||||
public DateTime ExpirationDate { get; set; }
|
||||
}
|
||||
@@ -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
|
||||
[MaxLength(ConfigurationConstraints.CountryNameLength)]
|
||||
[Required]
|
||||
public string DestinationCountry { get; set; } = null!;
|
||||
}
|
||||
@@ -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
|
||||
[MaxLength(ConfigurationConstraints.IssuerNameLength)]
|
||||
[Required]
|
||||
public string Issuer { get; set; } = null!;
|
||||
}
|
||||
@@ -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
|
||||
[MaxLength(ConfigurationConstraints.ReentryPermitNumberLength)]
|
||||
[Required]
|
||||
public string Number { get; set; } = null!;
|
||||
|
||||
/// Date when re-entry permit expires
|
||||
[Required]
|
||||
public DateTime ExpirationDate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using ApplicationLayer.InfrastructureServicesInterfaces;
|
||||
using Domains;
|
||||
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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Name of past visa can contain only english letters, digits and special symbols")
|
||||
.MaximumLength(ConfigurationConstraints.VisaNameLength)
|
||||
.WithMessage($"Past visa name length must be less than {ConfigurationConstraints.VisaNameLength}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Destination Country of past visit can contain only english letters, digits and special symbols")
|
||||
.MaximumLength(ConfigurationConstraints.CountryNameLength)
|
||||
.WithMessage($"Destination Country of past visit length must be less than {ConfigurationConstraints.CountryNameLength}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using ApplicationLayer.InfrastructureServicesInterfaces;
|
||||
using Domains;
|
||||
using 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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Issuer of permission for destination Country can contain only english letters, digits and special symbols")
|
||||
.MaximumLength(ConfigurationConstraints.IssuerNameLength)
|
||||
.WithMessage($"Issuer of permission to destination Country length must be less than {ConfigurationConstraints.IssuerNameLength}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using ApplicationLayer.InfrastructureServicesInterfaces;
|
||||
using Domains;
|
||||
using 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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Re-entry permit number can contain only english letters, digits and special symbols")
|
||||
.MaximumLength(ConfigurationConstraints.ReentryPermitNumberLength)
|
||||
.WithMessage($"Re-entry permit number length must be less than {ConfigurationConstraints.ReentryPermitNumberLength}");
|
||||
|
||||
RuleFor(p => p!.ExpirationDate)
|
||||
.NotEmpty()
|
||||
.WithMessage("Re-entry permit expiration date can not be empty")
|
||||
.GreaterThan(dateTimeProvider.Now())
|
||||
.WithMessage("Re-entry permit must not be expired");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using ApplicationLayer.Services.Applicants.Models;
|
||||
using Domains.VisaApplicationDomain;
|
||||
|
||||
namespace ApplicationLayer.Services.VisaApplications.Models;
|
||||
|
||||
/// Model of <see cref="VisaApplication" /> with applicant property
|
||||
public class VisaApplicationModel
|
||||
{
|
||||
/// <inheritdoc cref="VisaApplication.Id" />
|
||||
[Required]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// Applicant of application
|
||||
[Required]
|
||||
public ApplicantModel Applicant { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.Status" />
|
||||
[Required]
|
||||
public ApplicationStatus Status { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.ReentryPermit" />
|
||||
public ReentryPermitModel? ReentryPermit { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.DestinationCountry" />
|
||||
[Required]
|
||||
public string DestinationCountry { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.PastVisas" />
|
||||
[Required]
|
||||
public List<PastVisaModel> PastVisas { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.PermissionToDestCountry" />
|
||||
public PermissionToDestCountryModel? PermissionToDestCountry { get; set; }
|
||||
|
||||
[Required]
|
||||
public List<PastVisitModel> PastVisits { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.VisaCategory" />
|
||||
[Required]
|
||||
public VisaCategory VisaCategory { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.ForGroup" />
|
||||
[Required]
|
||||
public bool ForGroup { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries" />
|
||||
[Required]
|
||||
public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.RequestDate" />
|
||||
[Required]
|
||||
public DateTime RequestDate { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.ValidDaysRequested" />
|
||||
[Required]
|
||||
public int ValidDaysRequested { get; set; }
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Domains.VisaApplicationDomain;
|
||||
|
||||
namespace ApplicationLayer.Services.VisaApplications.Models;
|
||||
|
||||
/// Model of <see cref="VisaApplication"/>
|
||||
public class VisaApplicationModelForApplicant
|
||||
{
|
||||
/// <inheritdoc cref="VisaApplication.Id"/>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.Status"/>
|
||||
public ApplicationStatus Status { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.ReentryPermit"/>
|
||||
public ReentryPermit? ReentryPermit { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.DestinationCountry"/>
|
||||
public string DestinationCountry { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.PastVisas"/>
|
||||
public List<PastVisa> PastVisas { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.PermissionToDestCountry"/>
|
||||
public PermissionToDestCountry? PermissionToDestCountry { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.PastVisits"/>
|
||||
public List<PastVisit> PastVisits { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.VisaCategory"/>
|
||||
public VisaCategory VisaCategory { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.ForGroup"/>
|
||||
public bool ForGroup { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries"/>
|
||||
public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.RequestDate"/>
|
||||
public DateTime RequestDate { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.ValidDaysRequested"/>
|
||||
public int ValidDaysRequested { get; set; }
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using ApplicationLayer.Services.Applicants.Models;
|
||||
using Domains.VisaApplicationDomain;
|
||||
|
||||
namespace ApplicationLayer.Services.VisaApplications.Models;
|
||||
|
||||
/// Model of <see cref="VisaApplication"/> with applicant property
|
||||
public class VisaApplicationModelForAuthority
|
||||
{
|
||||
/// <inheritdoc cref="VisaApplication.Id"/>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// Applicant of application
|
||||
public ApplicantModel Applicant { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.Status"/>
|
||||
public ApplicationStatus Status { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.ReentryPermit"/>
|
||||
public ReentryPermit? ReentryPermit { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.DestinationCountry"/>
|
||||
public string DestinationCountry { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.PastVisas"/>
|
||||
public List<PastVisa> PastVisas { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.PermissionToDestCountry"/>
|
||||
public PermissionToDestCountry? PermissionToDestCountry { get; set; }
|
||||
|
||||
public List<PastVisit> PastVisits { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.VisaCategory"/>
|
||||
public VisaCategory VisaCategory { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.ForGroup"/>
|
||||
public bool ForGroup { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries"/>
|
||||
public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.RequestDate"/>
|
||||
public DateTime RequestDate { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.ValidDaysRequested"/>
|
||||
public int ValidDaysRequested { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Domains.VisaApplicationDomain;
|
||||
|
||||
namespace ApplicationLayer.Services.VisaApplications.Models;
|
||||
|
||||
/// Model of <see cref="VisaApplication" />
|
||||
public class VisaApplicationPreview
|
||||
{
|
||||
/// <inheritdoc cref="VisaApplication.Id" />
|
||||
[Required]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.Status" />
|
||||
[Required]
|
||||
public ApplicationStatus Status { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.DestinationCountry" />
|
||||
[Required]
|
||||
public string DestinationCountry { get; set; } = null!;
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.VisaCategory" />
|
||||
[Required]
|
||||
public VisaCategory VisaCategory { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.RequestDate" />
|
||||
[Required]
|
||||
public DateTime RequestDate { get; set; }
|
||||
|
||||
/// <inheritdoc cref="VisaApplication.ValidDaysRequested" />
|
||||
[Required]
|
||||
public int ValidDaysRequested { get; set; }
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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,23 @@ namespace ApplicationLayer.Services.VisaApplications.Requests.Validation;
|
||||
public class VisaApplicationCreateRequestValidator : AbstractValidator<VisaApplicationCreateRequest>
|
||||
{
|
||||
public VisaApplicationCreateRequestValidator(
|
||||
IValidator<ReentryPermit?> reentryPermitValidator,
|
||||
IValidator<PastVisa> pastVisaValidator,
|
||||
IValidator<PermissionToDestCountry?> permissionToDestCountryValidator,
|
||||
IValidator<PastVisit> pastVisitValidator,
|
||||
IValidator<ReentryPermitModel?> reentryPermitModelValidator,
|
||||
IValidator<PastVisaModel> pastVisaModelValidator,
|
||||
IValidator<PermissionToDestCountryModel?> permissionToDestCountryModelValidator,
|
||||
IValidator<PastVisitModel> pastVisitModelValidator,
|
||||
IApplicantsRepository applicants,
|
||||
IUserIdProvider userIdProvider)
|
||||
{
|
||||
RuleFor(r => r.PermissionToDestCountry)
|
||||
.NotEmpty()
|
||||
.WithMessage("For transit you must provide permission to destination country")
|
||||
.SetValidator(permissionToDestCountryModelValidator)
|
||||
.When(r => r.VisaCategory is VisaCategory.Transit);
|
||||
|
||||
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 +47,9 @@ public class VisaApplicationCreateRequestValidator : AbstractValidator<VisaAppli
|
||||
.WithMessage($"Valid days requested must be less than or equal to {ConfigurationConstraints.MaxValidDays}");
|
||||
|
||||
RuleForEach(r => r.PastVisas)
|
||||
.SetValidator(pastVisaValidator);
|
||||
|
||||
When(r => r.VisaCategory == VisaCategory.Transit,
|
||||
() =>
|
||||
RuleFor(r => r.PermissionToDestCountry)
|
||||
.SetValidator(permissionToDestCountryValidator));
|
||||
.SetValidator(pastVisaModelValidator);
|
||||
|
||||
RuleForEach(r => r.PastVisits)
|
||||
.SetValidator(pastVisitValidator);
|
||||
.SetValidator(pastVisitModelValidator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,37 @@
|
||||
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
|
||||
{
|
||||
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!;
|
||||
|
||||
public PermissionToDestCountryModel? PermissionToDestCountry { get; set; }
|
||||
|
||||
[Required]
|
||||
public PastVisitModel[] PastVisits { get; set; } = null!;
|
||||
}
|
||||
|
||||
16
SchengenVisaApi/BlazorWebAssemblyVisaApiClient/App.razor
Normal file
16
SchengenVisaApi/BlazorWebAssemblyVisaApiClient/App.razor
Normal file
@@ -0,0 +1,16 @@
|
||||
@using BlazorWebAssemblyVisaApiClient.ErrorHandling
|
||||
|
||||
<GlobalErrorHandler >
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
|
||||
</Found>
|
||||
<NotFound>
|
||||
<PageTitle>Not found</PageTitle>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
</GlobalErrorHandler>
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<PackageReference Include="Blazor.Bootstrap" Version="3.0.0" />
|
||||
<PackageReference Include="FluentValidation" Version="11.9.2" />
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" Version="3.2.0-rc1.20223.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.1" PrivateAssets="all"/>
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VisaApiClient\VisaApiClient.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="wwwroot\sample-data\weather.json" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css.map" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace BlazorWebAssemblyVisaApiClient.Common.Exceptions
|
||||
{
|
||||
public class BlazorClientException(string message) : Exception(message);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace BlazorWebAssemblyVisaApiClient.Common.Exceptions
|
||||
{
|
||||
public class NotLoggedInException() : BlazorClientException("User is not logged in.");
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
@using System.Net
|
||||
@using BlazorWebAssemblyVisaApiClient.ErrorHandling
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider
|
||||
@using VisaApiClient
|
||||
|
||||
@code {
|
||||
public static AuthData? AuthData;
|
||||
|
||||
[CascadingParameter] private GlobalErrorHandler ErrorHandler { get; set; } = null!;
|
||||
|
||||
[CascadingParameter] private Status? Status { get; set; }
|
||||
|
||||
[Inject] private Client Client { get; set; } = null!;
|
||||
|
||||
[Inject] private NavigationManager Nav { get; set; } = null!;
|
||||
|
||||
[Inject] private IUserDataProvider UserDataProvider { get; set; } = null!;
|
||||
|
||||
///Authorize with email and password
|
||||
/// <returns>Message to user</returns>
|
||||
public async Task TryAuthorize(AuthData authData)
|
||||
{
|
||||
Status?.SetMessage("Wait...");
|
||||
try
|
||||
{
|
||||
var token = await Client.LoginAsync(authData.Email, authData.Password);
|
||||
Client.AuthToken = token;
|
||||
AuthData = authData;
|
||||
UserDataProvider.UpdateCurrentRole();
|
||||
|
||||
Status?.SetSuccess("Logged in successfully.");
|
||||
}
|
||||
catch (ApiException<ProblemDetails> e)
|
||||
{
|
||||
if (e.Result.Status == (int)HttpStatusCode.Forbidden)
|
||||
{
|
||||
Status?.SetError(e.Result.Detail!);
|
||||
}
|
||||
else
|
||||
{
|
||||
Status?.SetError("Error occured");
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Status?.SetError("Error occured");
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void Logout()
|
||||
{
|
||||
Client.AuthToken = null;
|
||||
AuthData = null;
|
||||
try
|
||||
{
|
||||
UserDataProvider.UpdateCurrentRole();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
///Re-auth if token expired or something
|
||||
public async Task ReAuthenticate()
|
||||
{
|
||||
if (AuthData is not null)
|
||||
{
|
||||
await TryAuthorize(AuthData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Client.AuthToken = null;
|
||||
AuthData = null;
|
||||
Nav.NavigateTo("/");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
@using BlazorWebAssemblyVisaApiClient.ErrorHandling
|
||||
@using VisaApiClient
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter] protected GlobalErrorHandler ErrorHandler { get; set; } = null!;
|
||||
|
||||
[Inject] protected Client Client { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
@using VisaApiClient
|
||||
|
||||
<div>
|
||||
<div >
|
||||
<label >
|
||||
Country:<br/>
|
||||
<InputText class="rounded" @bind-Value="Address.Country"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => Address.Country"></ValidationMessage><br/>
|
||||
</div>
|
||||
|
||||
<div >
|
||||
<label >
|
||||
City:<br/>
|
||||
<InputText class="rounded" @bind-Value="Address.City"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => Address.City"></ValidationMessage><br/>
|
||||
</div>
|
||||
|
||||
<div >
|
||||
<label >
|
||||
Street:<br/>
|
||||
<InputText class="rounded" @bind-Value="Address.Street"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => Address.Street"></ValidationMessage><br/>
|
||||
</div>
|
||||
|
||||
<div >
|
||||
<label >
|
||||
Building:<br/>
|
||||
<InputText class="rounded" @bind-Value="Address.Building"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => Address.Building"></ValidationMessage>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired] public AddressModel Address { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
@using VisaApiClient
|
||||
|
||||
<div>
|
||||
<div >
|
||||
<label >
|
||||
Email:<br/>
|
||||
<InputText class="rounded" @bind-Value="AuthData.Email"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => AuthData.Email"></ValidationMessage><br/>
|
||||
</div>
|
||||
|
||||
<div >
|
||||
<label >
|
||||
Password:<br/>
|
||||
<InputText class="rounded" @bind-Value="AuthData.Password"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => AuthData.Password"></ValidationMessage>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired] public AuthData AuthData { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
@using System.Linq.Expressions
|
||||
@using System.Reflection
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Helpers
|
||||
@typeparam TItem where TItem : class
|
||||
@typeparam TMember where TMember : struct, Enum
|
||||
|
||||
<InputSelect TValue="TMember" @bind-Value="selected">
|
||||
@foreach (var value in enumValues)
|
||||
{
|
||||
<option value="@value.Key">@value.Value</option>
|
||||
}
|
||||
</InputSelect><br/>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired] public TItem Model { get; set; } = default!;
|
||||
|
||||
[Parameter, EditorRequired] public Expression<Func<TItem, TMember>> EnumProperty { get; set; } = null!;
|
||||
|
||||
[Parameter] public Action? OnChanged { get; set; }
|
||||
|
||||
private Dictionary<TMember, string> enumValues = new();
|
||||
private PropertyInfo modelMemberInfo = null!;
|
||||
private TMember selected;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var modelMemberName = ((MemberExpression)EnumProperty.Body).Member.Name;
|
||||
modelMemberInfo = typeof(TItem).GetProperty(modelMemberName)!;
|
||||
|
||||
foreach (var value in Enum.GetValues<TMember>())
|
||||
{
|
||||
enumValues.Add(value, value.GetDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
var current = (TMember)modelMemberInfo.GetValue(Model)!;
|
||||
if (!current.Equals(selected))
|
||||
{
|
||||
OnValueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
modelMemberInfo.SetValue(Model, selected);
|
||||
OnChanged?.Invoke();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
@using VisaApiClient
|
||||
|
||||
<div>
|
||||
<div >
|
||||
<label>
|
||||
First name@(Constants.RequiredFieldMarkup):<br/>
|
||||
<InputText DisplayName="First name" class="rounded" @bind-Value="Name.FirstName"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => Name.FirstName"></ValidationMessage>
|
||||
</div><br/>
|
||||
|
||||
<div >
|
||||
<label>
|
||||
Surname@(Constants.RequiredFieldMarkup):<br/>
|
||||
<InputText class="rounded" @bind-Value="Name.Surname"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => Name.Surname"></ValidationMessage>
|
||||
</div><br/>
|
||||
|
||||
<div >
|
||||
<label>
|
||||
Patronymic:<br/>
|
||||
<InputText class="rounded" @bind-Value="Name.Patronymic"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => Name.Patronymic"></ValidationMessage>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired] public NameModel Name { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.DateTimeProvider
|
||||
@using VisaApiClient
|
||||
|
||||
<div>
|
||||
<div >
|
||||
<label>
|
||||
Passport number:<br/>
|
||||
<InputText DisplayName="Passport number" class="rounded" @bind-Value="Passport.Number"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => Passport.Number"></ValidationMessage>
|
||||
</div><br/>
|
||||
|
||||
<div >
|
||||
<label>
|
||||
Issuer:<br/>
|
||||
<InputText class="rounded" @bind-Value="Passport.Issuer"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => Passport.Issuer"></ValidationMessage>
|
||||
</div><br/>
|
||||
|
||||
<div >
|
||||
<label>
|
||||
Issue date:<br/>
|
||||
<InputDate DisplayName="Issue date" class="rounded" @bind-Value="Passport.IssueDate" max="@formattedDate"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => Passport.IssueDate"></ValidationMessage>
|
||||
</div><br/>
|
||||
|
||||
<div >
|
||||
<label>
|
||||
Expiration date:<br/>
|
||||
<InputDate DisplayName="Expiration date" class="rounded" @bind-Value="Passport.ExpirationDate" min="@formattedDate"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => Passport.ExpirationDate"></ValidationMessage>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string formattedDate = null!;
|
||||
|
||||
[Parameter, EditorRequired] public PassportModel Passport { get; set; } = null!;
|
||||
|
||||
[Inject] IDateTimeProvider DateTimeProvider { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Passport.IssueDate = DateTime.Now;
|
||||
Passport.ExpirationDate = DateTime.Now;
|
||||
formattedDate = DateTimeProvider.FormattedNow();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
@using BlazorWebAssemblyVisaApiClient.Validation.Applicants.Models
|
||||
<div>
|
||||
<div >
|
||||
<label >
|
||||
Name:<br/>
|
||||
<InputText class="rounded" @bind-Value="PlaceOfWork.Name"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => PlaceOfWork.Name"></ValidationMessage><br/>
|
||||
</div>
|
||||
|
||||
<div >
|
||||
<label >
|
||||
Phone number:<br/>
|
||||
<InputText DisplayName="Phone number" class="rounded" @bind-Value="PlaceOfWork.PhoneNum"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => PlaceOfWork.PhoneNum"></ValidationMessage>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired] public PlaceOfWorkModel PlaceOfWork { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.DateTimeProvider
|
||||
@using VisaApiClient
|
||||
|
||||
<div>
|
||||
<label>
|
||||
Issuer:<br/>
|
||||
<InputText DisplayName="Issuer of permission to destination Country" class="rounded"
|
||||
@bind-Value="PermissionToDestCountry.Issuer"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => PermissionToDestCountry.Issuer"></ValidationMessage><br/>
|
||||
|
||||
<label>
|
||||
Expiration date:<br/>
|
||||
<InputDate DisplayName="Expiration date of permission to destination Country" class="rounded"
|
||||
@bind-Value="PermissionToDestCountry.ExpirationDate"
|
||||
min="@formattedDate"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => PermissionToDestCountry.ExpirationDate"></ValidationMessage>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string formattedDate = null!;
|
||||
|
||||
[Parameter, EditorRequired] public PermissionToDestCountryModel PermissionToDestCountry { get; set; } = null!;
|
||||
|
||||
[Inject] IDateTimeProvider DateTimeProvider { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
formattedDate = DateTimeProvider.FormattedNow();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.DateTimeProvider
|
||||
@using VisaApiClient
|
||||
|
||||
<div>
|
||||
<label>
|
||||
Number:<br/>
|
||||
<InputText DisplayName="Number of re-entry permit" class="rounded"
|
||||
@bind-Value="ReentryPermit.Number"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => ReentryPermit.Number"></ValidationMessage><br/>
|
||||
|
||||
<label>
|
||||
Expiration date:<br/>
|
||||
<InputDate DisplayName="Expiration date of re-entry permit" class="rounded"
|
||||
@bind-Value="ReentryPermit.ExpirationDate"
|
||||
min="@formattedDate"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => ReentryPermit.ExpirationDate"></ValidationMessage>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string formattedDate = null!;
|
||||
|
||||
[Parameter, EditorRequired] public ReentryPermitModel ReentryPermit { get; set; } = null!;
|
||||
|
||||
[Inject] IDateTimeProvider DateTimeProvider { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
formattedDate = DateTimeProvider.FormattedNow();
|
||||
ReentryPermit.ExpirationDate = DateTimeProvider.Now();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<p class="@statusClass">@((MarkupString)StatusText)</p>
|
||||
<CascadingValue Value="this">
|
||||
@ChildContent
|
||||
</CascadingValue>
|
||||
|
||||
@code {
|
||||
private string statusClass = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
public string StatusText { get; private set; } = string.Empty;
|
||||
|
||||
public void SetMessage(string message)
|
||||
{
|
||||
statusClass = string.Empty;
|
||||
StatusText = message;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public void SetError(string message)
|
||||
{
|
||||
statusClass = "validation-message";
|
||||
StatusText = message;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public void SetSuccess(string message)
|
||||
{
|
||||
statusClass = "text-success";
|
||||
StatusText = message;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
20
SchengenVisaApi/BlazorWebAssemblyVisaApiClient/Constants.cs
Normal file
20
SchengenVisaApi/BlazorWebAssemblyVisaApiClient/Constants.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public readonly static Regex EnglishWordRegex = new("^[a-zA-Z]*$");
|
||||
|
||||
public readonly static Regex EnglishPhraseRegex = new(@"^[a-zA-Z№0-9?><;,{}[\]\-_+=!@#$%\^&*|']*$");
|
||||
|
||||
public readonly static Regex PhoneNumRegex = new(@"^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$");
|
||||
|
||||
public readonly static MarkupString RequiredFieldMarkup = (MarkupString)"<span style=\"color: red;\">*</span>";
|
||||
|
||||
public const string ApplicantRole = "Applicant";
|
||||
public const string ApprovingAuthorityRole = "ApprovingAuthority";
|
||||
public const string AdminRole = "Admin";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
@using System.Net
|
||||
@using BlazorWebAssemblyVisaApiClient.Common.Exceptions
|
||||
@using VisaApiClient
|
||||
|
||||
<CascadingValue Value="this">
|
||||
<Modal @ref="modal">
|
||||
<BodyTemplate>
|
||||
@errorDetails
|
||||
</BodyTemplate>
|
||||
<FooterTemplate>
|
||||
<Button Color="ButtonColor.Secondary" @onclick="modal.HideAsync">Okaaaay</Button>
|
||||
</FooterTemplate>
|
||||
</Modal>
|
||||
@ChildContent
|
||||
</CascadingValue>
|
||||
|
||||
@code
|
||||
{
|
||||
private Modal modal = null!;
|
||||
private string errorDetails = null!;
|
||||
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
[Inject] private NavigationManager Nav { get; set; } = null!;
|
||||
|
||||
public void Handle(Exception ex)
|
||||
{
|
||||
switch (ex)
|
||||
{
|
||||
case ApiException<ProblemDetails>
|
||||
{
|
||||
StatusCode: (int)HttpStatusCode.Unauthorized or (int)HttpStatusCode.Forbidden
|
||||
} or NotLoggedInException:
|
||||
Nav.NavigateTo("/");
|
||||
modal.Title = "Authorization failed";
|
||||
errorDetails = "You are not authorized or your authorization is expired";
|
||||
modal.ShowAsync();
|
||||
break;
|
||||
|
||||
case ApiException<ProblemDetails> problemDetails:
|
||||
modal.Title = problemDetails.Result.Title!;
|
||||
errorDetails = problemDetails.Result.Detail!;
|
||||
modal.ShowAsync();
|
||||
break;
|
||||
|
||||
default:
|
||||
modal.Title = "Something went wrong";
|
||||
errorDetails = "Please, text an email with your problem definition on nasrudin@mail.ru";
|
||||
modal.ShowAsync();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using AutoMapper;
|
||||
using BlazorWebAssemblyVisaApiClient.Validation.Applicants.Models;
|
||||
using VisaApiClient;
|
||||
using PlaceOfWorkModel = BlazorWebAssemblyVisaApiClient.Validation.Applicants.Models.PlaceOfWorkModel;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.AutoMapper.Profiles
|
||||
{
|
||||
public class RegisterApplicantRequestProfile : Profile
|
||||
{
|
||||
public RegisterApplicantRequestProfile()
|
||||
{
|
||||
CreateMap<RegisterApplicantRequestModel, RegisterApplicantRequest>(MemberList.Destination);
|
||||
|
||||
CreateMap<RegisterRequestModel, RegisterRequest>(MemberList.Destination);
|
||||
|
||||
CreateMap<PlaceOfWorkModel, VisaApiClient.PlaceOfWorkModel>(MemberList.Destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using AutoMapper;
|
||||
using BlazorWebAssemblyVisaApiClient.Validation.VisaApplications.Models;
|
||||
using VisaApiClient;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.AutoMapper.Profiles
|
||||
{
|
||||
public class VisaApplicationCreateRequestProfile : Profile
|
||||
{
|
||||
public VisaApplicationCreateRequestProfile()
|
||||
{
|
||||
CreateMap<VisaApplicationCreateRequestModel, VisaApplicationCreateRequest>(MemberList.Destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.Helpers
|
||||
{
|
||||
public static class EnumExtensions
|
||||
{
|
||||
public static string GetDisplayName(this Enum value)
|
||||
{
|
||||
var enumMembers = value.GetType().GetMembers();
|
||||
var member = enumMembers.First(info => info.Name == value.ToString());
|
||||
var displayAttribute = (DisplayAttribute?)member
|
||||
.GetCustomAttributes(typeof(DisplayAttribute), false)
|
||||
.FirstOrDefault();
|
||||
var displayName = displayAttribute?.Name ?? value.ToString();
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Text;
|
||||
using FluentValidation.Results;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.Helpers
|
||||
{
|
||||
public static class ValidationResultExtensions
|
||||
{
|
||||
public static string ToErrorsString(this ValidationResult validationResult)
|
||||
=> ErrorsToString(validationResult.Errors.Select(e => e.ErrorMessage));
|
||||
|
||||
private static string ErrorsToString(IEnumerable<string> errors)
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
foreach (var error in errors)
|
||||
{
|
||||
stringBuilder.Append($"{error}<br/>");
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.Services.DateTimeProvider
|
||||
{
|
||||
public class DateTimeProvider : IDateTimeProvider
|
||||
{
|
||||
public DateTime Now() => DateTime.Now;
|
||||
|
||||
public string FormattedNow() => Now().ToString("yyyy-MM-dd");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.Services.DateTimeProvider
|
||||
{
|
||||
public interface IDateTimeProvider
|
||||
{
|
||||
DateTime Now();
|
||||
|
||||
string FormattedNow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using BlazorWebAssemblyVisaApiClient.Common.Exceptions;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider.Exceptions
|
||||
{
|
||||
public class UnknownRoleException() : BlazorClientException("Unknown user role");
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using VisaApiClient;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider
|
||||
{
|
||||
public interface IUserDataProvider
|
||||
{
|
||||
public string? CurrentRole { get; }
|
||||
|
||||
public Action? OnRoleChanged { get; set; }
|
||||
|
||||
public Task<ApplicantModel> GetApplicant();
|
||||
|
||||
public void UpdateCurrentRole();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider.Exceptions;
|
||||
using VisaApiClient;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider
|
||||
{
|
||||
public class UserDataProvider(Client client) : IUserDataProvider
|
||||
{
|
||||
private readonly static JwtSecurityTokenHandler tokenHandler = new();
|
||||
|
||||
public string? CurrentRole { get; private set; }
|
||||
|
||||
public Action? OnRoleChanged { get; set; }
|
||||
|
||||
public async Task<ApplicantModel> GetApplicant()
|
||||
{
|
||||
return await client.GetApplicantAsync();
|
||||
}
|
||||
|
||||
public void UpdateCurrentRole()
|
||||
{
|
||||
var role = CurrentRole;
|
||||
|
||||
if (client.AuthToken is null)
|
||||
{
|
||||
if (CurrentRole is not null)
|
||||
{
|
||||
role = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var token = tokenHandler.ReadJwtToken(client.AuthToken.Token);
|
||||
role = token.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Role)?.Value;
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Constants.ApplicantRole: break;
|
||||
case Constants.ApprovingAuthorityRole: break;
|
||||
case Constants.AdminRole: break;
|
||||
default: throw new UnknownRoleException();
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentRole != role)
|
||||
{
|
||||
CurrentRole = role;
|
||||
OnRoleChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
@using BlazorWebAssemblyVisaApiClient.Components.Auth
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider
|
||||
@inherits LayoutComponentBase
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<NavMenu/>
|
||||
</div>
|
||||
|
||||
<main class="fullscreen">
|
||||
<div class="top-row px-4">
|
||||
<AuthComponent @ref="authComponent"/>
|
||||
@if (UserDataProvider.CurrentRole is not null)
|
||||
{
|
||||
<p>
|
||||
Logged as @UserDataProvider.CurrentRole (@AuthComponent.AuthData?.Email)
|
||||
<button class="btn-secondary" @onclick="authComponent.Logout">Log out</button>
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<NavLink href="/">Log in</NavLink>
|
||||
}
|
||||
</div>
|
||||
|
||||
<article class="content px-4">
|
||||
@Body
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private AuthComponent authComponent = null!;
|
||||
|
||||
[Inject] private IUserDataProvider UserDataProvider { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
UserDataProvider.OnRoleChanged += StateHasChanged;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider
|
||||
<div class="top-row ps-3 navbar navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="">Schengen Visa</a>
|
||||
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
|
||||
<nav class="flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Login
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
@if (UserDataProvider.CurrentRole is Constants.ApplicantRole or Constants.ApprovingAuthorityRole)
|
||||
{
|
||||
<nav class="flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="applications" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Applications
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
@if (UserDataProvider.CurrentRole is Constants.ApplicantRole)
|
||||
{
|
||||
<nav class="flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="applications/new" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> New application
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
@if (UserDataProvider.CurrentRole is Constants.AdminRole)
|
||||
{
|
||||
<nav class="flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="authorities" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Authorities
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
@if (UserDataProvider.CurrentRole is Constants.AdminRole)
|
||||
{
|
||||
<nav class="flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="authorities/add" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Add authority
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool collapseNavMenu = true;
|
||||
private string? currentRole = null!;
|
||||
|
||||
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||
|
||||
[Inject] private IUserDataProvider UserDataProvider { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
UserDataProvider.OnRoleChanged += StateHasChanged;
|
||||
}
|
||||
|
||||
private void ToggleNavMenu()
|
||||
{
|
||||
collapseNavMenu = !collapseNavMenu;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
.navbar-toggler {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin-right: 0.75rem;
|
||||
top: -1px;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.bi-house-door-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-plus-square-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-list-nested-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-item:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-item:last-of-type {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a {
|
||||
color: #d7d7d7;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.37);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep a:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
/* Allow sidebar to scroll for tall menus */
|
||||
height: calc(100vh - 3.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
@page "/authorities/add"
|
||||
@using AutoMapper
|
||||
@using BlazorWebAssemblyVisaApiClient.Validation.Applicants.Models
|
||||
@using VisaApiClient
|
||||
@using BlazorWebAssemblyVisaApiClient.Components
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Helpers
|
||||
@using FluentValidation
|
||||
@inherits BlazorWebAssemblyVisaApiClient.Components.Base.VisaClientComponentBase
|
||||
|
||||
<EditForm Model="requestModel" class="with-centered-content">
|
||||
<ObjectGraphDataAnnotationsValidator/>
|
||||
<div >
|
||||
<label>
|
||||
Email:<br/>
|
||||
<InputText class="rounded" @bind-Value="requestModel.AuthData.Email"/>
|
||||
<ValidationMessage For="() => requestModel.AuthData.Email"/>
|
||||
</label><br/>
|
||||
<p/>
|
||||
|
||||
<label>
|
||||
Password:<br/>
|
||||
<InputText class="rounded" @bind-Value="requestModel.AuthData.Password"/>
|
||||
<ValidationMessage For="() => requestModel.AuthData.Password"/>
|
||||
</label><br/>
|
||||
<p/>
|
||||
|
||||
<button class="btn-primary rounded" @onclick="Add">Add</button><br/>
|
||||
<Status @ref="status"/>
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@code
|
||||
{
|
||||
private RegisterRequestModel requestModel = new();
|
||||
private Status status = new();
|
||||
|
||||
[Inject] private IValidator<RegisterRequestModel> RegisterRequestModelValidator { get; set; } = null!;
|
||||
[Inject] private IMapper Mapper { get; set; } = null!;
|
||||
|
||||
private async Task Add()
|
||||
{
|
||||
var validationResult = await RegisterRequestModelValidator.ValidateAsync(requestModel);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
status.SetError(validationResult.ToErrorsString());
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
status.SetMessage("Wait...");
|
||||
await Client.RegisterAuthorityAsync(Mapper.Map<RegisterRequest>(requestModel));
|
||||
status.SetSuccess("Success");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
@page "/applications/{ApplicationId}"
|
||||
@using System.Net
|
||||
@using BlazorWebAssemblyVisaApiClient.Common.Exceptions
|
||||
@using BlazorWebAssemblyVisaApiClient.Components
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Helpers
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider
|
||||
@using VisaApiClient
|
||||
@inherits BlazorWebAssemblyVisaApiClient.Components.Base.VisaClientComponentBase
|
||||
|
||||
<PageTitle>Application</PageTitle>
|
||||
|
||||
<table class="table table-bordered table-hover table-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td >
|
||||
Applicant's fullname:<br/>
|
||||
<em>@NameToString(application.Applicant.Name)</em>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
Date of birth:<br/>
|
||||
<em>@application.Applicant.BirthDate.ToString("d")</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
Country and city of birth:<br/>
|
||||
<em>@application.Applicant.Passport.Number</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
Citizenship:<br/>
|
||||
<em>@application.Applicant.Citizenship</em>
|
||||
</td>
|
||||
<td >
|
||||
Citizenship by birth:<br/>
|
||||
<em>@application.Applicant.CitizenshipByBirth</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td >
|
||||
Gender:<br/>
|
||||
<em>@application.Applicant.Gender.GetDisplayName()</em>
|
||||
</td>
|
||||
<td >
|
||||
Marital status:<br/>
|
||||
<em>@application.Applicant.MaritalStatus.GetDisplayName()</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td >
|
||||
Father's fullname:<br/>
|
||||
<em>@NameToString(application.Applicant.FatherName)</em>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
Mother's fullname:<br/>
|
||||
<em>@NameToString(application.Applicant.MotherName)</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td >
|
||||
Passport number:<br/>
|
||||
<em>@application.Applicant.Passport.Number</em>
|
||||
</td>
|
||||
<td >
|
||||
Issue date:<br/>
|
||||
<em>@application.Applicant.Passport.IssueDate.ToString("d")</em>
|
||||
</td>
|
||||
<td >
|
||||
Expiration date:<br/>
|
||||
<em>@application.Applicant.Passport.ExpirationDate.ToString("d")</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
Passport issuer:<br/>
|
||||
<em>@application.Applicant.Passport.Issuer</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
Re-entry permission (for non-residents):<br/>
|
||||
@if (application.Applicant.IsNonResident)
|
||||
{
|
||||
<em>@(application.ReentryPermit is null ? "None" : $"{application.ReentryPermit.Number}, expires at {application.ReentryPermit.ExpirationDate:d}")</em>
|
||||
}
|
||||
else
|
||||
{
|
||||
<em>Not non-resident</em>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
Job title:<br/>
|
||||
<em>@application.Applicant.JobTitle</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
Place of work, address, hirer's phone number:<br/>
|
||||
<em>
|
||||
@((MarkupString)$"{application.Applicant.PlaceOfWork.Name}<br>Address: {AddressToString(application.Applicant.PlaceOfWork.Address)}<br>Phone num: {application.Applicant.PlaceOfWork.PhoneNum}")
|
||||
</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td >
|
||||
Destination Country:<br/>
|
||||
<em>@application.DestinationCountry</em>
|
||||
</td>
|
||||
<td >
|
||||
Visa category:<br/>
|
||||
<em>@application.VisaCategory</em>
|
||||
</td>
|
||||
<td >
|
||||
Visa:<br/>
|
||||
<em>@(application.ForGroup ? "For group" : "Individual")</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td >
|
||||
Requested number of entries:<br/>
|
||||
<em>@application.RequestedNumberOfEntries.GetDisplayName()</em>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
Valid for:<br/>
|
||||
<em>@($"{application.ValidDaysRequested} days")</em>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
Past visas:<br/>
|
||||
@if (application.PastVisas.Any())
|
||||
{
|
||||
foreach (var visa in application.PastVisas)
|
||||
{
|
||||
<em>@($"{visa.Name} issued at {visa.IssueDate:d} and was valid until {visa.ExpirationDate:d}")</em>
|
||||
<br/>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<em>None</em>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
Permission to destination Country, if transit:<br/>
|
||||
@if (application.VisaCategory is VisaCategory.Transit)
|
||||
{
|
||||
<em>@(application.PermissionToDestCountry is null ? "None" : $"Expires at {application.PermissionToDestCountry.ExpirationDate}, issued by: {application.PermissionToDestCountry.Issuer}")</em>
|
||||
}
|
||||
else
|
||||
{
|
||||
<em>Non-transit</em>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
Past visits:<br/>
|
||||
@if (application.PastVisas.Any())
|
||||
{
|
||||
foreach (var visit in application.PastVisits)
|
||||
{
|
||||
<em>@($"Visit to {visit.DestinationCountry}, entered at {visit.StartDate:d} and lasts until {visit.EndDate:d}")</em>
|
||||
<br/>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<em>None</em>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@if (currentRole == Constants.ApprovingAuthorityRole)
|
||||
{
|
||||
<button class="btn-outline-primary" @onclick="Approve">Approve</button>
|
||||
<button class="btn-outline-danger" @onclick="Reject">Reject</button>
|
||||
<Status @ref="status"/>
|
||||
}
|
||||
|
||||
@code {
|
||||
private VisaApplicationModel application = new();
|
||||
private string currentRole = null!;
|
||||
private Status status = null!;
|
||||
|
||||
[Parameter] public string ApplicationId { get; set; } = null!;
|
||||
|
||||
[Inject] private IUserDataProvider UserDataProvider { get; set; } = null!;
|
||||
|
||||
[Inject] private NavigationManager Nav { get; set; } = null!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var applicationId = Guid.Parse(ApplicationId);
|
||||
currentRole = UserDataProvider.CurrentRole ?? throw new NotLoggedInException();
|
||||
|
||||
application = currentRole switch
|
||||
{
|
||||
Constants.ApplicantRole => await Client.GetApplicationForApplicantAsync(applicationId),
|
||||
Constants.ApprovingAuthorityRole => await Client.GetApplicationForAuthorityAsync(applicationId),
|
||||
_ => throw new NotLoggedInException()
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
if (e is ApiException<ProblemDetails> { Result.Status: (int)HttpStatusCode.Conflict })
|
||||
{
|
||||
Nav.NavigateTo("/applications");
|
||||
}
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static string NameToString(NameModel name)
|
||||
=> $"{name.FirstName} {name.Surname} {name.Patronymic}".TrimEnd();
|
||||
|
||||
private static string AddressToString(AddressModel address)
|
||||
=> $"{address.Country}, {address.City}, {address.Street} {address.Building}";
|
||||
|
||||
private async void Approve()
|
||||
{
|
||||
try
|
||||
{
|
||||
status.SetMessage("Wait...");
|
||||
await Client.SetStatusFromAuthorityAsync(application.Id, AuthorityRequestStatuses.Approved);
|
||||
Nav.NavigateTo("/applications");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
status.SetError("Error occured.");
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async void Reject()
|
||||
{
|
||||
try
|
||||
{
|
||||
status.SetMessage("Wait...");
|
||||
await Client.SetStatusFromAuthorityAsync(application.Id, AuthorityRequestStatuses.Rejected);
|
||||
Nav.NavigateTo("/applications");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
status.SetError("Error occured.");
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
@page "/applications"
|
||||
@using BlazorWebAssemblyVisaApiClient.Common.Exceptions
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Helpers
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider
|
||||
@using VisaApiClient
|
||||
@inherits BlazorWebAssemblyVisaApiClient.Components.Base.VisaClientComponentBase
|
||||
|
||||
<PageTitle>Applications</PageTitle>
|
||||
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Destination Country</th>
|
||||
<th>Visa Category</th>
|
||||
<th>Request date</th>
|
||||
<th>Days requested</th>
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@foreach (var application in applications)
|
||||
{
|
||||
var rowClass = application.Status switch
|
||||
{
|
||||
ApplicationStatus.Pending => "",
|
||||
ApplicationStatus.Approved => "table-success",
|
||||
ApplicationStatus.Rejected => "table-danger",
|
||||
ApplicationStatus.Closed => "table-danger",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
<tr class="@rowClass">
|
||||
<td>@application.DestinationCountry</td>
|
||||
<td>@application.VisaCategory.GetDisplayName()</td>
|
||||
<td>@application.RequestDate.ToString("d")</td>
|
||||
<td>@application.ValidDaysRequested</td>
|
||||
<td>@application.Status.GetDisplayName()</td>
|
||||
<td>
|
||||
<NavLink href="@($"/applications/{application.Id}")">
|
||||
<button class="btn-outline-primary">See</button>
|
||||
</NavLink>
|
||||
@if (currentRole == Constants.ApplicantRole && application.Status is ApplicationStatus.Pending)
|
||||
{
|
||||
<span> | </span>
|
||||
<input type="button" class="border-danger" @onclick="() => CloseApplication(application)" value="Close"/>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table >
|
||||
|
||||
@code {
|
||||
private string currentRole = null!;
|
||||
private List<VisaApplicationPreview> applications = [];
|
||||
|
||||
[Inject] private IUserDataProvider UserDataProvider { get; set; } = null!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
currentRole = UserDataProvider.CurrentRole ?? throw new NotLoggedInException();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
await Fetch();
|
||||
}
|
||||
|
||||
private async Task Fetch()
|
||||
{
|
||||
try
|
||||
{
|
||||
applications = currentRole switch
|
||||
{
|
||||
Constants.ApplicantRole => (await Client.GetApplicationsForApplicantAsync()).OrderByDescending(a => a.RequestDate).ToList(),
|
||||
Constants.ApprovingAuthorityRole => (await Client.GetPendingAsync()).OrderByDescending(a => a.RequestDate).ToList(),
|
||||
_ => throw new NotLoggedInException()
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CloseApplication(VisaApplicationPreview application)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Client.CloseApplicationAsync(application.Id);
|
||||
application.Status = ApplicationStatus.Closed;
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
@page "/"
|
||||
@using BlazorWebAssemblyVisaApiClient.Components.Auth
|
||||
@using VisaApiClient
|
||||
@using BlazorWebAssemblyVisaApiClient.Components.FormComponents.Applicants
|
||||
@using BlazorWebAssemblyVisaApiClient.Components
|
||||
@inherits BlazorWebAssemblyVisaApiClient.Components.Base.VisaClientComponentBase
|
||||
|
||||
<PageTitle>Authentication</PageTitle>
|
||||
|
||||
<div class="with-centered-content">
|
||||
<EditForm class="form" Model="loginData" OnValidSubmit="TryLogin">
|
||||
<DataAnnotationsValidator/>
|
||||
|
||||
<AuthDataInput AuthData="loginData"/><br/>
|
||||
|
||||
<input class="btn-outline-primary rounded" type="submit" value="Login"/>
|
||||
or
|
||||
<NavLink href="register">Register</NavLink >
|
||||
<Status><AuthComponent @ref="auth"/></Status>
|
||||
</EditForm>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private AuthData loginData = new();
|
||||
private AuthComponent auth = null!;
|
||||
|
||||
private async Task TryLogin()
|
||||
{
|
||||
await auth.TryAuthorize(loginData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
@page "/authorities"
|
||||
@using VisaApiClient
|
||||
@inherits BlazorWebAssemblyVisaApiClient.Components.Base.VisaClientComponentBase
|
||||
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Email</th><th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var authority in authorities)
|
||||
{
|
||||
var path = $"authorities/{authority.Id}/{authority.Email}";
|
||||
<tr>
|
||||
<td>@authority.Email</td>
|
||||
<td>
|
||||
<NavLink href="@path">
|
||||
<button class="btn-outline-primary">Change</button>
|
||||
</NavLink>
|
||||
|
|
||||
<button class="btn-outline-danger" @onclick="() => Delete(authority)">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@code {
|
||||
private List<UserModel> authorities = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
authorities = (await Client.GetAuthorityAccountsAsync()).ToList();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Delete(UserModel authority)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Client.RemoveAuthorityAccountAsync(authority.Id);
|
||||
authorities.Remove(authority);
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
@page "/authorities/{authorityId}/{oldEmail}"
|
||||
@inherits BlazorWebAssemblyVisaApiClient.Components.Base.VisaClientComponentBase
|
||||
@using BlazorWebAssemblyVisaApiClient.Common.Exceptions
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider
|
||||
@using VisaApiClient
|
||||
@using BlazorWebAssemblyVisaApiClient.Components
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Helpers
|
||||
@using FluentValidation
|
||||
|
||||
<EditForm Model="model" class="with-centered-content">
|
||||
<div >
|
||||
<label>
|
||||
New email:<br/>
|
||||
<InputText class="rounded" @bind-Value="model.Email"/>
|
||||
</label><br/><p/>
|
||||
|
||||
<label>
|
||||
New password (leave blank if shouldn't be changed):<br/>
|
||||
<InputText class="rounded" @bind-Value="model.Password"/>
|
||||
</label><br/><p/>
|
||||
|
||||
<button class="btn-primary rounded" @onclick="Save">Save</button><br/>
|
||||
<Status @ref="status"/>
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
@code
|
||||
{
|
||||
private Status status = null!;
|
||||
private ChangeAuthData model = new();
|
||||
|
||||
[Parameter] public string AuthorityId { get; set; } = null!;
|
||||
|
||||
[Parameter] public string OldEmail { get; set; } = null!;
|
||||
|
||||
[Inject] private IUserDataProvider UserDataProvider { get; set; } = null!;
|
||||
|
||||
[Inject] private IValidator<ChangeUserAuthDataRequest> ChangeUserAuthDataRequestValidator { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (UserDataProvider.CurrentRole is not Constants.AdminRole)
|
||||
{
|
||||
throw new NotLoggedInException();
|
||||
}
|
||||
|
||||
model.Email = OldEmail;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
var request = new ChangeUserAuthDataRequest
|
||||
{
|
||||
UserId = Guid.Parse(AuthorityId),
|
||||
NewAuthData = model
|
||||
};
|
||||
|
||||
var validationResult = await ChangeUserAuthDataRequestValidator.ValidateAsync(request);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
status.SetError(validationResult.ToErrorsString());
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
status.SetMessage("Wait...");
|
||||
await Client.ChangeAuthorityAuthDataAsync(request);
|
||||
status.SetSuccess("Success");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
@page "/applications/new"
|
||||
@using System.Net
|
||||
@using AutoMapper
|
||||
@using BlazorWebAssemblyVisaApiClient.Validation.VisaApplications.Models
|
||||
@using BlazorWebAssemblyVisaApiClient.Components.FormComponents.Applicants
|
||||
@using VisaApiClient
|
||||
@using BlazorWebAssemblyVisaApiClient.Components
|
||||
@using BlazorWebAssemblyVisaApiClient.Components.FormComponents.VisaApplications
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Helpers
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.DateTimeProvider
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider
|
||||
@using BlazorWebAssemblyVisaApiClient.Validation
|
||||
@using FluentValidation
|
||||
@using Newtonsoft.Json.Linq
|
||||
@inherits BlazorWebAssemblyVisaApiClient.Components.Base.VisaClientComponentBase
|
||||
|
||||
<PageTitle>New Application</PageTitle>
|
||||
|
||||
<div class="horizontal-centered-content">
|
||||
<h3>New application</h3>
|
||||
<EditForm class="form" Model="requestModel" OnValidSubmit="TryCreate">
|
||||
<ObjectGraphDataAnnotationsValidator/>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Visa@(Constants.RequiredFieldMarkup)</h5>
|
||||
<label>
|
||||
Destination Country:<br/>
|
||||
<InputText DisplayName="Destination Country" class="rounded" @bind-Value="requestModel.DestinationCountry"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => requestModel.DestinationCountry"></ValidationMessage><br/>
|
||||
|
||||
<label>
|
||||
Category:
|
||||
<EnumInputList Model="requestModel"
|
||||
EnumProperty="r => r.VisaCategory"
|
||||
OnChanged="StateHasChanged"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => requestModel.VisaCategory"></ValidationMessage><br/>
|
||||
|
||||
<label>
|
||||
Number of entries: <EnumInputList Model="requestModel" EnumProperty="r => r.RequestedNumberOfEntries"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => requestModel.RequestedNumberOfEntries"></ValidationMessage><br/>
|
||||
|
||||
<label>
|
||||
For group: <InputCheckbox @bind-Value="requestModel.IsForGroup"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => requestModel.IsForGroup"></ValidationMessage><br/>
|
||||
|
||||
<label>
|
||||
Valid for days:<br/>
|
||||
<InputNumber DisplayName="Valid days" class="rounded" @bind-Value="requestModel.ValidDaysRequested"/>
|
||||
</label>
|
||||
<ValidationMessage For="() => requestModel.ValidDaysRequested"></ValidationMessage><br/>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Past visas</h5>
|
||||
@if (requestModel.PastVisas.Count > 0)
|
||||
{
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th><th>Issue date</th><th>Expiration date</th><th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var visa in requestModel.PastVisas)
|
||||
{
|
||||
<tr>
|
||||
<td>@visa.Name</td>
|
||||
<td>@visa.IssueDate.ToString("d.MM.yyyy")</td>
|
||||
<td>@visa.ExpirationDate.ToString("d.MM.yyyy")</td>
|
||||
<td>
|
||||
<input type="button" class="border-danger" @onclick="() => RemovePastVisa(visa)" value="X"/>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
<label>
|
||||
Name:<br/>
|
||||
<InputText DisplayName="Past visa name" class="rounded" @bind-Value="editableVisa.Name"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => editableVisa.Name"></ValidationMessage><br/>
|
||||
|
||||
<label>
|
||||
Issue date:<br/>
|
||||
<InputDate DisplayName="Past visa issue date"
|
||||
class="rounded"
|
||||
@bind-Value="editableVisa.IssueDate"
|
||||
max="@formattedNow"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => editableVisa.IssueDate"></ValidationMessage><br/>
|
||||
|
||||
<label>
|
||||
Expiration date:<br/>
|
||||
<InputDate DisplayName="Past visa expiration date"
|
||||
class="rounded"
|
||||
@bind-Value="editableVisa.ExpirationDate"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => editableVisa.ExpirationDate"></ValidationMessage><br/>
|
||||
|
||||
<input type="button" class="btn-outline-primary rounded"
|
||||
disabled="@(requestModel.PastVisas.Count == ConfigurationConstraints.MaxPastVisas)"
|
||||
@onclick="AddPastVisa" value="Add"/>
|
||||
<Status @ref="pastVisaStatus"/>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Past visits</h5>
|
||||
@if (requestModel.PastVisits.Count > 0)
|
||||
{
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Destination Country</th><th>Start date</th><th>End date</th><th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var visit in requestModel.PastVisits)
|
||||
{
|
||||
<tr>
|
||||
<td>@visit.DestinationCountry</td>
|
||||
<td>@visit.StartDate.ToString("d.MM.yyyy")</td>
|
||||
<td>@visit.EndDate.ToString("d.MM.yyyy")</td>
|
||||
<td>
|
||||
<input type="button" class="border-danger" @onclick="() => RemovePastVisit(visit)" value="X"/>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
<label>
|
||||
Destination Country:<br/>
|
||||
<InputText DisplayName="Past visit destination Country" class="rounded" @bind-Value="editableVisit.DestinationCountry"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => editableVisit.DestinationCountry"></ValidationMessage><br/>
|
||||
|
||||
<label>
|
||||
Start date:<br/>
|
||||
<InputDate DisplayName="Past visit start date"
|
||||
class="rounded"
|
||||
@bind-Value="editableVisit.StartDate"
|
||||
max="@formattedNow"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => editableVisit.StartDate"></ValidationMessage><br/>
|
||||
|
||||
<label>
|
||||
End date:<br/>
|
||||
<InputDate DisplayName="Past visit end date"
|
||||
class="rounded"
|
||||
@bind-Value="editableVisit.EndDate"
|
||||
max="@formattedNow"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => editableVisit.EndDate"></ValidationMessage><br/>
|
||||
|
||||
<input type="button" class="btn-outline-primary rounded"
|
||||
disabled="@(requestModel.PastVisits.Count == ConfigurationConstraints.MaxPastVisits)"
|
||||
@onclick="AddPastVisit" value="Add"/>
|
||||
<Status @ref="pastVisitStatus"/>
|
||||
</div>
|
||||
|
||||
@if (requestModel.VisaCategory is VisaCategory.Transit)
|
||||
{
|
||||
requestModel.PermissionToDestCountry ??= NewPermissionToDestCountry();
|
||||
<div class="form-block">
|
||||
<h5>Permission to destination Country@(Constants.RequiredFieldMarkup)</h5>
|
||||
<PermissionToDestCountryInput PermissionToDestCountry="requestModel.PermissionToDestCountry"/>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
requestModel.PermissionToDestCountry = null;
|
||||
}
|
||||
|
||||
@if (isNonResident)
|
||||
{
|
||||
requestModel.ReentryPermit ??= NewReentryPermit();
|
||||
<div class="form-block">
|
||||
<h5>Re-entry permission@(Constants.RequiredFieldMarkup)</h5>
|
||||
<ReentryPermitInput ReentryPermit="requestModel.ReentryPermit"/>
|
||||
</div>
|
||||
}
|
||||
|
||||
<br/><input type="submit" class="btn-outline-primary rounded" value="Register"/>
|
||||
<ValidationSummary/>
|
||||
<Status @ref="status"/>
|
||||
</EditForm>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
//todo past visas and visits
|
||||
private VisaApplicationCreateRequestModel requestModel = new();
|
||||
private Status status = null!;
|
||||
private Status pastVisaStatus = null!;
|
||||
private Status pastVisitStatus = null!;
|
||||
private bool isNonResident;
|
||||
private string formattedNow = null!;
|
||||
private PastVisaModel editableVisa = null!;
|
||||
private PastVisitModel editableVisit = null!;
|
||||
|
||||
[Inject] IDateTimeProvider DateTimeProvider { get; set; } = null!;
|
||||
|
||||
[Inject] IUserDataProvider UserDataProvider { get; set; } = null!;
|
||||
|
||||
[Inject] IValidator<VisaApplicationCreateRequestModel> VisaApplicationCreateRequestValidator { get; set; } = null!;
|
||||
|
||||
[Inject] IValidator<PastVisaModel> PastVisaModelValidator { get; set; } = null!;
|
||||
|
||||
[Inject] IValidator<PastVisitModel> PastVisitModelValidator { get; set; } = null!;
|
||||
|
||||
[Inject] IMapper Mapper { get; set; } = null!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
editableVisa = NewPastVisa();
|
||||
editableVisit = NewPastVisit();
|
||||
requestModel.PermissionToDestCountry = NewPermissionToDestCountry();
|
||||
formattedNow = DateTimeProvider.FormattedNow();
|
||||
|
||||
try
|
||||
{
|
||||
isNonResident = (await UserDataProvider.GetApplicant()).IsNonResident;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TryCreate()
|
||||
{
|
||||
var validationResult = await VisaApplicationCreateRequestValidator.ValidateAsync(requestModel);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
var errorsString = validationResult.ToErrorsString();
|
||||
status.SetError(errorsString);
|
||||
}
|
||||
|
||||
status.SetMessage("Wait...");
|
||||
|
||||
var request = Mapper.Map<VisaApplicationCreateRequest>(requestModel);
|
||||
try
|
||||
{
|
||||
await Client.CreateApplicationAsync(request);
|
||||
status.SetSuccess("Application created successfully.");
|
||||
}
|
||||
catch (ApiException<ProblemDetails> e)
|
||||
{
|
||||
if (e.StatusCode == (int)HttpStatusCode.BadRequest
|
||||
&& e.Result.AdditionalProperties.TryGetValue("errors", out var errors))
|
||||
{
|
||||
try
|
||||
{
|
||||
var errorsList = ((JArray)errors).ToObject<List<string>>();
|
||||
status.SetError(string.Join("<br/>", errorsList!));
|
||||
}
|
||||
catch (Exception inner)
|
||||
{
|
||||
ErrorHandler.Handle(inner);
|
||||
status.SetError("Error occured");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PastVisaModel NewPastVisa()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExpirationDate = DateTimeProvider.Now(),
|
||||
IssueDate = DateTimeProvider.Now()
|
||||
};
|
||||
}
|
||||
|
||||
private ReentryPermitModel NewReentryPermit()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExpirationDate = DateTimeProvider.Now()
|
||||
};
|
||||
}
|
||||
|
||||
private PermissionToDestCountryModel NewPermissionToDestCountry()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExpirationDate = DateTimeProvider.Now()
|
||||
};
|
||||
}
|
||||
|
||||
private PastVisitModel NewPastVisit()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
StartDate = DateTimeProvider.Now(),
|
||||
EndDate = DateTimeProvider.Now()
|
||||
};
|
||||
}
|
||||
|
||||
private void AddPastVisa()
|
||||
{
|
||||
if (requestModel.PastVisas.Count >= ConfigurationConstraints.MaxPastVisas)
|
||||
{
|
||||
pastVisaStatus.SetError($"{ConfigurationConstraints.MaxPastVisas} past visas is maximum");
|
||||
return;
|
||||
}
|
||||
|
||||
var validationResult = PastVisaModelValidator.Validate(editableVisa);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
pastVisaStatus.SetError(validationResult.ToErrorsString());
|
||||
return;
|
||||
}
|
||||
|
||||
requestModel.PastVisas.Add(editableVisa);
|
||||
editableVisa = NewPastVisa();
|
||||
pastVisaStatus.SetSuccess("Added successfully");
|
||||
}
|
||||
|
||||
private void RemovePastVisa(PastVisaModel visa)
|
||||
{
|
||||
requestModel.PastVisas.Remove(visa);
|
||||
}
|
||||
|
||||
private void AddPastVisit()
|
||||
{
|
||||
if (requestModel.PastVisits.Count >= ConfigurationConstraints.MaxPastVisits)
|
||||
{
|
||||
pastVisitStatus.SetError($"{ConfigurationConstraints.MaxPastVisits} past visits is maximum");
|
||||
return;
|
||||
}
|
||||
|
||||
var validationResult = PastVisitModelValidator.Validate(editableVisit);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
pastVisitStatus.SetError(validationResult.ToErrorsString());
|
||||
return;
|
||||
}
|
||||
|
||||
requestModel.PastVisits.Add(editableVisit);
|
||||
editableVisit = NewPastVisit();
|
||||
pastVisitStatus.SetSuccess("Added successfully");
|
||||
}
|
||||
|
||||
private void RemovePastVisit(PastVisitModel visit)
|
||||
{
|
||||
requestModel.PastVisits.Remove(visit);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
@page "/register"
|
||||
@using System.Net
|
||||
@using AutoMapper
|
||||
@using VisaApiClient
|
||||
@using BlazorWebAssemblyVisaApiClient.Components.FormComponents.Applicants
|
||||
@using global::FluentValidation
|
||||
@using Newtonsoft.Json
|
||||
@using Newtonsoft.Json.Linq
|
||||
@using BlazorWebAssemblyVisaApiClient.Components
|
||||
@using BlazorWebAssemblyVisaApiClient.Infrastructure.Helpers
|
||||
@using BlazorWebAssemblyVisaApiClient.Validation
|
||||
@using BlazorWebAssemblyVisaApiClient.Validation.Applicants.Models
|
||||
@inherits BlazorWebAssemblyVisaApiClient.Components.Base.VisaClientComponentBase
|
||||
|
||||
<PageTitle>Registration</PageTitle>
|
||||
|
||||
<div class="horizontal-centered-content">
|
||||
<h3>Registration data</h3>
|
||||
<EditForm class="form" Model="requestModel" OnValidSubmit="TryRegisterApplicant">
|
||||
<ObjectGraphDataAnnotationsValidator/>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Authentication data@(Constants.RequiredFieldMarkup)</h5>
|
||||
<AuthDataInput AuthData="requestModel.RegisterRequest.AuthData"/>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Your Fullname</h5>
|
||||
<NameInput Name="requestModel.ApplicantName"/>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Fullname of your mother</h5>
|
||||
<NameInput Name="requestModel.MotherName"/>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Fullname of your father</h5>
|
||||
<NameInput Name="requestModel.FatherName"/>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Your passport@(Constants.RequiredFieldMarkup)</h5>
|
||||
<PassportInput Passport="requestModel.Passport"/>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Birth data@(Constants.RequiredFieldMarkup)</h5>
|
||||
<div >
|
||||
<label>
|
||||
Country of birth:<br/>
|
||||
<InputText DisplayName="Country of birth" class="rounded" @bind-Value="requestModel.CountryOfBirth"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => requestModel.CountryOfBirth"></ValidationMessage><br/>
|
||||
<label>
|
||||
City of birth:<br/>
|
||||
<InputText DisplayName="City of birth" class="rounded" @bind-Value="requestModel.CityOfBirth"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => requestModel.CityOfBirth"></ValidationMessage><br/>
|
||||
<label>
|
||||
Birth date:<br/>
|
||||
<InputDate DisplayName="Birth date" class="rounded" @bind-Value="requestModel.BirthDate" max="@formattedMaxBirthdayDate"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => requestModel.BirthDate"></ValidationMessage>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Citizenship@(Constants.RequiredFieldMarkup)</h5>
|
||||
<div >
|
||||
<label>
|
||||
Citizenship:<br/>
|
||||
<InputText class="rounded" @bind-Value="requestModel.Citizenship"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => requestModel.Citizenship"></ValidationMessage><br/>
|
||||
<label>
|
||||
Citizenship by birth:<br/>
|
||||
<InputText DisplayName="Citizenship by birth" class="rounded" @bind-Value="requestModel.CitizenshipByBirth"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => requestModel.CitizenshipByBirth"></ValidationMessage>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Address of your place of work@(Constants.RequiredFieldMarkup)</h5>
|
||||
<div >
|
||||
<AddressInput Address="requestModel.PlaceOfWork.Address"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Place of work data@(Constants.RequiredFieldMarkup)</h5>
|
||||
<div >
|
||||
<PlaceOfWorkInput PlaceOfWork="requestModel.PlaceOfWork"/><br/>
|
||||
|
||||
<label>
|
||||
Job title:<br/>
|
||||
<InputText DisplayName="Job title" class="rounded" @bind-Value="requestModel.JobTitle"/>
|
||||
</label><br/>
|
||||
<ValidationMessage For="() => requestModel.JobTitle"></ValidationMessage>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-block">
|
||||
<h5>Other</h5>
|
||||
|
||||
<div >
|
||||
<label>
|
||||
Gender: <EnumInputList Model="requestModel" EnumProperty="r => r.Gender"/>
|
||||
</label>
|
||||
</div><br/>
|
||||
|
||||
<div >
|
||||
<label>
|
||||
Marital status: <EnumInputList Model="requestModel" EnumProperty="r => r.MaritalStatus"/>
|
||||
</label>
|
||||
</div><br/>
|
||||
|
||||
<div >
|
||||
<label>
|
||||
Non-resident: <InputCheckbox @bind-Value="requestModel.IsNonResident"/>
|
||||
</label>
|
||||
</div>
|
||||
</div><br/>
|
||||
|
||||
<input type="submit" class="btn-outline-primary" value="Register"/>
|
||||
<Status @ref="status"/>
|
||||
</EditForm>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private RegisterApplicantRequestModel requestModel = new();
|
||||
private Status status = null!;
|
||||
private string formattedMaxBirthdayDate = null!;
|
||||
|
||||
[Inject] IValidator<RegisterApplicantRequestModel> RegisterApplicantRequestValidator { get; set; } = null!;
|
||||
|
||||
[Inject] IMapper Mapper { get; set; } = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
requestModel.BirthDate = DateTime.Now.AddYears(-ConfigurationConstraints.ApplicantMinAge);
|
||||
formattedMaxBirthdayDate = requestModel.BirthDate.ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
private async void TryRegisterApplicant()
|
||||
{
|
||||
var validationResult = await RegisterApplicantRequestValidator.ValidateAsync(requestModel);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
var errorsString = validationResult.ToErrorsString();
|
||||
status.SetError(errorsString);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
status.SetMessage("Wait...");
|
||||
|
||||
var request = Mapper.Map<RegisterApplicantRequest>(requestModel);
|
||||
try
|
||||
{
|
||||
await Client.RegisterAsync(request);
|
||||
status.SetSuccess("Register successful. Now log in.");
|
||||
}
|
||||
catch (ApiException<ProblemDetails> e)
|
||||
{
|
||||
if (e.StatusCode == (int)HttpStatusCode.BadRequest
|
||||
&& e.Result.AdditionalProperties.TryGetValue("errors", out var errors))
|
||||
{
|
||||
var errorsList = ((JArray)errors).ToObject<List<string>>();
|
||||
if (errorsList is null)
|
||||
{
|
||||
ErrorHandler.Handle(new JsonException("Can't convert validation errors to list"));
|
||||
return;
|
||||
}
|
||||
|
||||
status.SetError(string.Join("<br/>", errorsList));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
status.SetError("Error occured");
|
||||
ErrorHandler.Handle(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
SchengenVisaApi/BlazorWebAssemblyVisaApiClient/Program.cs
Normal file
35
SchengenVisaApi/BlazorWebAssemblyVisaApiClient/Program.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.DateTimeProvider;
|
||||
using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.UserDataProvider;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using VisaApiClient;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
|
||||
//todo move to launch settings
|
||||
const string baseAddress = "https://localhost:44370";
|
||||
|
||||
//todo make pretty
|
||||
builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(baseAddress) });
|
||||
builder.Services.AddBlazorBootstrap();
|
||||
builder.Services.AddScoped<Client>(sp => new Client(baseAddress, sp.GetRequiredService<HttpClient>()));
|
||||
|
||||
builder.Services.AddSingleton<IDateTimeProvider, DateTimeProvider>();
|
||||
builder.Services.AddScoped<IUserDataProvider, UserDataProvider>();
|
||||
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
|
||||
|
||||
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:25927",
|
||||
"sslPort": 44345
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "http://localhost:5038",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:7200;http://localhost:5038",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using VisaApiClient;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Validation.Applicants.Models
|
||||
{
|
||||
/// Model of place of work with attributes required for validation to work
|
||||
public class PlaceOfWorkModel
|
||||
{
|
||||
[Required]
|
||||
[StringLength(ConfigurationConstraints.PlaceOfWorkNameLength, MinimumLength = 1)]
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
[Required]
|
||||
[ValidateComplexType]
|
||||
public AddressModel Address { get; set; } = new AddressModel();
|
||||
|
||||
[Required]
|
||||
[StringLength(ConfigurationConstraints.PhoneNumberLength, MinimumLength = ConfigurationConstraints.PhoneNumberMinLength)]
|
||||
public string PhoneNum { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using VisaApiClient;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Validation.Applicants.Models
|
||||
{
|
||||
/// Model of request with attributes required for validation to work
|
||||
public class RegisterApplicantRequestModel
|
||||
{
|
||||
[Required]
|
||||
[ValidateComplexType]
|
||||
public RegisterRequestModel RegisterRequest { get; set; } = new();
|
||||
|
||||
[Required]
|
||||
[ValidateComplexType]
|
||||
public NameModel ApplicantName { get; set; } = new();
|
||||
|
||||
[Required]
|
||||
[ValidateComplexType]
|
||||
public PassportModel Passport { get; set; } = new();
|
||||
|
||||
[Required(AllowEmptyStrings = true)]
|
||||
public DateTimeOffset BirthDate { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(70, MinimumLength = 1)]
|
||||
public string CityOfBirth { get; set; } = default!;
|
||||
|
||||
[Required]
|
||||
[StringLength(70, MinimumLength = 1)]
|
||||
public string CountryOfBirth { get; set; } = default!;
|
||||
|
||||
[Required]
|
||||
[StringLength(30, MinimumLength = 1)]
|
||||
public string Citizenship { get; set; } = default!;
|
||||
|
||||
[Required]
|
||||
[StringLength(30, MinimumLength = 1)]
|
||||
public string CitizenshipByBirth { get; set; } = default!;
|
||||
|
||||
[Required(AllowEmptyStrings = true)]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public Gender Gender { get; set; }
|
||||
|
||||
[Required(AllowEmptyStrings = true)]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public MaritalStatus MaritalStatus { get; set; }
|
||||
|
||||
[Required]
|
||||
[ValidateComplexType]
|
||||
public NameModel FatherName { get; set; } = new();
|
||||
|
||||
[Required]
|
||||
[ValidateComplexType]
|
||||
public NameModel MotherName { get; set; } = new();
|
||||
|
||||
[Required]
|
||||
[StringLength(50, MinimumLength = 1)]
|
||||
public string JobTitle { get; set; } = default!;
|
||||
|
||||
[Required]
|
||||
[ValidateComplexType]
|
||||
public PlaceOfWorkModel PlaceOfWork { get; set; } = new();
|
||||
|
||||
public bool IsNonResident { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using VisaApiClient;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Validation.Applicants.Models
|
||||
{
|
||||
/// Model of request with attributes required for validation to work
|
||||
public class RegisterRequestModel
|
||||
{
|
||||
[Required]
|
||||
[ValidateComplexType]
|
||||
public AuthData AuthData { get; set; } = new AuthData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using FluentValidation;
|
||||
using VisaApiClient;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Validation.Applicants.Validators;
|
||||
|
||||
public class NameModelValidator : AbstractValidator<NameModel>
|
||||
{
|
||||
public NameModelValidator()
|
||||
{
|
||||
RuleFor(m => m.FirstName)
|
||||
.NotEmpty()
|
||||
.WithMessage("First Name can not be empty")
|
||||
.Matches(Constants.EnglishWordRegex)
|
||||
.WithMessage("First name must be in english characters")
|
||||
.MaximumLength(ConfigurationConstraints.NameLength)
|
||||
.WithMessage($"First Name length must be less than {ConfigurationConstraints.NameLength}");
|
||||
|
||||
RuleFor(m => m.Surname)
|
||||
.NotEmpty()
|
||||
.WithMessage("Surname can not be empty")
|
||||
.Matches(Constants.EnglishWordRegex)
|
||||
.WithMessage("Surname must be in english characters")
|
||||
.MaximumLength(ConfigurationConstraints.NameLength)
|
||||
.WithMessage($"Surname length must be less than {ConfigurationConstraints.NameLength}");
|
||||
|
||||
RuleFor(m => m.Patronymic)
|
||||
.Matches(Constants.EnglishWordRegex)
|
||||
.WithMessage("Patronymic must be in english characters")
|
||||
.MaximumLength(ConfigurationConstraints.NameLength)
|
||||
.WithMessage($"Patronymic length must be less than {ConfigurationConstraints.NameLength}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using BlazorWebAssemblyVisaApiClient.Infrastructure.Services.DateTimeProvider;
|
||||
using FluentValidation;
|
||||
using VisaApiClient;
|
||||
|
||||
namespace BlazorWebAssemblyVisaApiClient.Validation.Applicants.Validators;
|
||||
|
||||
public class PassportModelValidator : AbstractValidator<PassportModel>
|
||||
{
|
||||
public PassportModelValidator(IDateTimeProvider dateTimeProvider)
|
||||
{
|
||||
RuleFor(r => r.Issuer)
|
||||
.NotEmpty()
|
||||
.WithMessage("Passport issuer can not be empty")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Passport issuer field can contain only english letters, digits and special symbols")
|
||||
.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")
|
||||
.Matches(Constants.EnglishPhraseRegex)
|
||||
.WithMessage("Passport number field can contain only english letters, digits and special symbols")
|
||||
.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");
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user