file-scoped namespaces

This commit is contained in:
2024-08-26 11:33:31 +03:00
parent bfce112a59
commit 0d8e30004d
56 changed files with 650 additions and 708 deletions

View File

@@ -1,4 +1,3 @@
namespace ApplicationLayer.GeneralExceptions
{
public class AlreadyExistsException(string message) : ApiException(message);
}
namespace ApplicationLayer.GeneralExceptions;
public class AlreadyExistsException(string message) : ApiException(message);

View File

@@ -1,4 +1,3 @@
namespace ApplicationLayer.GeneralExceptions
{
public class ApiException(string message) : Exception(message);
}
namespace ApplicationLayer.GeneralExceptions;
public class ApiException(string message) : Exception(message);

View File

@@ -1,8 +1,7 @@
namespace ApplicationLayer.InfrastructureServicesInterfaces
namespace ApplicationLayer.InfrastructureServicesInterfaces;
public interface IDateTimeProvider
{
public interface IDateTimeProvider
{
/// Returns current date and time
DateTime Now();
}
/// Returns current date and time
DateTime Now();
}

View File

@@ -1,8 +1,7 @@
namespace ApplicationLayer.InfrastructureServicesInterfaces
namespace ApplicationLayer.InfrastructureServicesInterfaces;
public interface IUserIdProvider
{
public interface IUserIdProvider
{
/// Returns identifier of authenticated user who sent the request
Guid GetUserId();
}
/// Returns identifier of authenticated user who sent the request
Guid GetUserId();
}

View File

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

View File

@@ -1,16 +1,15 @@
using Domains.ApplicantDomain;
namespace ApplicationLayer.Services.Applicants.Models
namespace ApplicationLayer.Services.Applicants.Models;
public class PlaceOfWorkModel
{
public class PlaceOfWorkModel
{
/// Name of hirer
public string Name { get; set; } = null!;
/// Name of hirer
public string Name { get; set; } = null!;
/// Address of hirer
public Address Address { get; set; } = null!;
/// Address of hirer
public Address Address { get; set; } = null!;
/// Phone number of hirer
public string PhoneNum { get; set; } = null!;
}
/// Phone number of hirer
public string PhoneNum { get; set; } = null!;
}

View File

@@ -1,4 +1,3 @@
namespace ApplicationLayer.Services.AuthServices.Common
{
public record AuthData(string Email, string Password);
}
namespace ApplicationLayer.Services.AuthServices.Common;
public record AuthData(string Email, string Password);

View File

@@ -2,12 +2,12 @@
using ApplicationLayer.Services.AuthServices.NeededServices;
using Domains.Users;
namespace ApplicationLayer.Services.AuthServices.LoginService
namespace ApplicationLayer.Services.AuthServices.LoginService;
public class DevelopmentLoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService
{
public class DevelopmentLoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService
async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken)
{
async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken)
{
if (email == "admin@mail.ru" && password == "admin")
{
var admin = new User { Role = Role.Admin };
@@ -23,5 +23,4 @@ namespace ApplicationLayer.Services.AuthServices.LoginService
return tokenGenerator.CreateToken(user);
}
}
}

View File

@@ -1,6 +1,5 @@
using ApplicationLayer.GeneralExceptions;
namespace ApplicationLayer.Services.AuthServices.LoginService.Exceptions
{
public class IncorrectLoginDataException() : ApiException("Incorrect email or password");
}
namespace ApplicationLayer.Services.AuthServices.LoginService.Exceptions;
public class IncorrectLoginDataException() : ApiException("Incorrect email or password");

View File

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

View File

@@ -1,13 +1,13 @@
using ApplicationLayer.Services.AuthServices.LoginService.Exceptions;
using ApplicationLayer.Services.AuthServices.NeededServices;
namespace ApplicationLayer.Services.AuthServices.LoginService
namespace ApplicationLayer.Services.AuthServices.LoginService;
/// <inheritdoc cref="ILoginService"/>
public class LoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService
{
/// <inheritdoc cref="ILoginService"/>
public class LoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService
async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken)
{
async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken)
{
var user = await users.FindByEmailAsync(email, cancellationToken);
if (user is null || user.Password != password)
{
@@ -16,5 +16,4 @@ namespace ApplicationLayer.Services.AuthServices.LoginService
return tokenGenerator.CreateToken(user);
}
}
}

View File

@@ -1,9 +1,8 @@
using Domains.Users;
namespace ApplicationLayer.Services.AuthServices.NeededServices
namespace ApplicationLayer.Services.AuthServices.NeededServices;
public interface ITokenGenerator
{
public interface ITokenGenerator
{
string CreateToken(User user);
}
string CreateToken(User user);
}

View File

@@ -1,21 +1,20 @@
using ApplicationLayer.InfrastructureServicesInterfaces;
using Domains.Users;
namespace ApplicationLayer.Services.AuthServices.NeededServices
{
/// Repository pattern for <see cref="User"/>
public interface IUsersRepository : IGenericRepository<User>
{
/// Find <see cref="User"/> by email
/// <param name="email"><see cref="User"/>'s email</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>User or null if not found</returns>
Task<User?> FindByEmailAsync(string email, CancellationToken cancellationToken);
namespace ApplicationLayer.Services.AuthServices.NeededServices;
/// Returns all accounts with specific role
/// <param name="role">role</param>
/// <param name="cancellationToken">cancellation token</param>
/// <returns>list of accounts</returns>
Task<List<User>> GetAllOfRoleAsync(Role role, CancellationToken cancellationToken);
}
/// Repository pattern for <see cref="User"/>
public interface IUsersRepository : IGenericRepository<User>
{
/// Find <see cref="User"/> by email
/// <param name="email"><see cref="User"/>'s email</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>User or null if not found</returns>
Task<User?> FindByEmailAsync(string email, CancellationToken cancellationToken);
/// Returns all accounts with specific role
/// <param name="role">role</param>
/// <param name="cancellationToken">cancellation token</param>
/// <returns>list of accounts</returns>
Task<List<User>> GetAllOfRoleAsync(Role role, CancellationToken cancellationToken);
}

View File

@@ -1,14 +1,13 @@
using ApplicationLayer.Services.AuthServices.Requests;
namespace ApplicationLayer.Services.AuthServices.RegisterService
{
/// Handles register request
public interface IRegisterService
{
/// Handle <see cref="RegisterApplicantRequest"/>
Task RegisterApplicant(RegisterApplicantRequest request, CancellationToken cancellationToken);
namespace ApplicationLayer.Services.AuthServices.RegisterService;
/// Handles <see cref="RegisterRequest"/> and adds approving authority account
Task RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken);
}
/// Handles register request
public interface IRegisterService
{
/// Handle <see cref="RegisterApplicantRequest"/>
Task RegisterApplicant(RegisterApplicantRequest request, CancellationToken cancellationToken);
/// Handles <see cref="RegisterRequest"/> and adds approving authority account
Task RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken);
}

View File

@@ -6,37 +6,36 @@ using AutoMapper;
using Domains.ApplicantDomain;
using Domains.Users;
namespace ApplicationLayer.Services.AuthServices.RegisterService
namespace ApplicationLayer.Services.AuthServices.RegisterService;
/// <inheritdoc cref="IRegisterService"/>
public class RegisterService(
IUsersRepository users,
IApplicantsRepository applicants,
IUnitOfWork unitOfWork,
IMapper mapper) : IRegisterService
{
/// <inheritdoc cref="IRegisterService"/>
public class RegisterService(
IUsersRepository users,
IApplicantsRepository applicants,
IUnitOfWork unitOfWork,
IMapper mapper) : IRegisterService
async Task IRegisterService.RegisterApplicant(RegisterApplicantRequest request, CancellationToken cancellationToken)
{
async Task IRegisterService.RegisterApplicant(RegisterApplicantRequest request, CancellationToken cancellationToken)
{
var user = mapper.Map<User>(request.AuthData);
user.Role = Role.Applicant;
var user = mapper.Map<User>(request.AuthData);
user.Role = Role.Applicant;
var applicant = mapper.Map<Applicant>(request);
applicant.UserId = user.Id;
var applicant = mapper.Map<Applicant>(request);
applicant.UserId = user.Id;
await users.AddAsync(user, cancellationToken);
await applicants.AddAsync(applicant, cancellationToken);
await users.AddAsync(user, cancellationToken);
await applicants.AddAsync(applicant, cancellationToken);
await unitOfWork.SaveAsync(cancellationToken);
}
await unitOfWork.SaveAsync(cancellationToken);
}
async Task IRegisterService.RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken)
{
var user = mapper.Map<User>(request.AuthData);
user.Role = Role.ApprovingAuthority;
async Task IRegisterService.RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken)
{
var user = mapper.Map<User>(request.AuthData);
user.Role = Role.ApprovingAuthority;
await users.AddAsync(user, cancellationToken);
await users.AddAsync(user, cancellationToken);
await unitOfWork.SaveAsync(cancellationToken);
}
await unitOfWork.SaveAsync(cancellationToken);
}
}

View File

@@ -2,22 +2,21 @@
using ApplicationLayer.Services.AuthServices.Common;
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);
}
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);

View File

@@ -1,6 +1,5 @@
using ApplicationLayer.Services.AuthServices.Common;
namespace ApplicationLayer.Services.AuthServices.Requests
{
public record RegisterRequest(AuthData AuthData);
}
namespace ApplicationLayer.Services.AuthServices.Requests;
public record RegisterRequest(AuthData AuthData);

View File

@@ -3,12 +3,12 @@ using ApplicationLayer.Services.AuthServices.NeededServices;
using Domains;
using FluentValidation;
namespace ApplicationLayer.Services.AuthServices.Requests.Validation
namespace ApplicationLayer.Services.AuthServices.Requests.Validation;
public class AuthDataValidator : AbstractValidator<AuthData>
{
public class AuthDataValidator : AbstractValidator<AuthData>
public AuthDataValidator(IUsersRepository users)
{
public AuthDataValidator(IUsersRepository users)
{
RuleFor(d => d.Email)
.NotEmpty()
.WithMessage("Email can not be empty")
@@ -28,5 +28,4 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation
.MaximumLength(ConfigurationConstraints.PasswordLength)
.WithMessage($"Password length must be less than {ConfigurationConstraints.PasswordLength}");
}
}
}

View File

@@ -2,12 +2,12 @@
using Domains.ApplicantDomain;
using FluentValidation;
namespace ApplicationLayer.Services.AuthServices.Requests.Validation
namespace ApplicationLayer.Services.AuthServices.Requests.Validation;
public class NameValidator : AbstractValidator<Name>
{
public class NameValidator : AbstractValidator<Name>
public NameValidator()
{
public NameValidator()
{
RuleFor(m => m.FirstName)
.NotEmpty()
.WithMessage("First Name can not be empty")
@@ -24,5 +24,4 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation
.MaximumLength(ConfigurationConstraints.NameLength)
.WithMessage($"Patronymic length must be less than {ConfigurationConstraints.NameLength}");
}
}
}

View File

@@ -3,12 +3,12 @@ using Domains;
using Domains.ApplicantDomain;
using FluentValidation;
namespace ApplicationLayer.Services.AuthServices.Requests.Validation
namespace ApplicationLayer.Services.AuthServices.Requests.Validation;
public class PassportValidator : AbstractValidator<Passport>
{
public class PassportValidator : AbstractValidator<Passport>
public PassportValidator(IDateTimeProvider dateTimeProvider)
{
public PassportValidator(IDateTimeProvider dateTimeProvider)
{
RuleFor(r => r.Issuer)
.NotEmpty()
.WithMessage("Passport issuer can not be empty")
@@ -33,5 +33,4 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation
.LessThanOrEqualTo(dateTimeProvider.Now())
.WithMessage("Passport issue date must be in past");
}
}
}

View File

@@ -2,12 +2,12 @@
using Domains;
using FluentValidation;
namespace ApplicationLayer.Services.AuthServices.Requests.Validation
namespace ApplicationLayer.Services.AuthServices.Requests.Validation;
public class PlaceOfWorkModelValidator : AbstractValidator<PlaceOfWorkModel>
{
public class PlaceOfWorkModelValidator : AbstractValidator<PlaceOfWorkModel>
public PlaceOfWorkModelValidator()
{
public PlaceOfWorkModelValidator()
{
RuleFor(p => p.Name)
.NotEmpty()
.WithMessage("Place of work name can not be empty")
@@ -46,5 +46,4 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation
.MaximumLength(ConfigurationConstraints.CountryNameLength)
.WithMessage($"Building of place of work length must be less than {ConfigurationConstraints.BuildingNumberLength}");
}
}
}

View File

@@ -5,17 +5,17 @@ using Domains;
using Domains.ApplicantDomain;
using FluentValidation;
namespace ApplicationLayer.Services.AuthServices.Requests.Validation
namespace ApplicationLayer.Services.AuthServices.Requests.Validation;
public class RegisterApplicantRequestValidator : AbstractValidator<RegisterApplicantRequest>
{
public class RegisterApplicantRequestValidator : AbstractValidator<RegisterApplicantRequest>
public RegisterApplicantRequestValidator(
IDateTimeProvider dateTimeProvider,
IValidator<Name> nameValidator,
IValidator<AuthData> authDataValidator,
IValidator<Passport> passportValidator,
IValidator<PlaceOfWorkModel> placeOfWorkModelValidator)
{
public RegisterApplicantRequestValidator(
IDateTimeProvider dateTimeProvider,
IValidator<Name> nameValidator,
IValidator<AuthData> authDataValidator,
IValidator<Passport> passportValidator,
IValidator<PlaceOfWorkModel> placeOfWorkModelValidator)
{
RuleFor(r => r.AuthData)
.SetValidator(authDataValidator);
@@ -74,5 +74,4 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation
RuleFor(r => r.PlaceOfWork)
.SetValidator(placeOfWorkModelValidator);
}
}
}

View File

@@ -1,23 +1,22 @@
using ApplicationLayer.Services.Users.Requests;
using Domains.Users;
namespace ApplicationLayer.Services.Users
namespace ApplicationLayer.Services.Users;
/// user accounts service
public interface IUsersService
{
/// user accounts service
public interface IUsersService
{
/// Returns all user accounts with role of approving authority
/// <param name="cancellationToken">Cancellation token</param>
Task<List<User>> GetAuthoritiesAccountsAsync(CancellationToken cancellationToken);
/// Returns all user accounts with role of approving authority
/// <param name="cancellationToken">Cancellation token</param>
Task<List<User>> GetAuthoritiesAccountsAsync(CancellationToken cancellationToken);
/// Changes authentication data for an 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);
/// Changes authentication data for an 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);
/// Removes user account
/// <param name="userId">Identifier of account</param>
/// <param name="cancellationToken">Cancellation token</param>
Task RemoveUserAccount(Guid userId, CancellationToken cancellationToken);
}
/// Removes user account
/// <param name="userId">Identifier of account</param>
/// <param name="cancellationToken">Cancellation token</param>
Task RemoveUserAccount(Guid userId, CancellationToken cancellationToken);
}

View File

@@ -1,6 +1,5 @@
using ApplicationLayer.Services.AuthServices.Common;
namespace ApplicationLayer.Services.Users.Requests
{
public record ChangeUserAuthDataRequest(Guid UserId, AuthData NewAuthData);
}
namespace ApplicationLayer.Services.Users.Requests;
public record ChangeUserAuthDataRequest(Guid UserId, AuthData NewAuthData);

View File

@@ -3,17 +3,17 @@ using ApplicationLayer.Services.AuthServices.NeededServices;
using ApplicationLayer.Services.Users.Requests;
using Domains.Users;
namespace ApplicationLayer.Services.Users
namespace ApplicationLayer.Services.Users;
public class UsersService(IUsersRepository users, IUnitOfWork unitOfWork) : IUsersService
{
public class UsersService(IUsersRepository users, IUnitOfWork unitOfWork) : IUsersService
async Task<List<User>> IUsersService.GetAuthoritiesAccountsAsync(CancellationToken cancellationToken)
{
async Task<List<User>> IUsersService.GetAuthoritiesAccountsAsync(CancellationToken cancellationToken)
{
return await users.GetAllOfRoleAsync(Role.ApprovingAuthority, cancellationToken);
}
async Task IUsersService.ChangeAccountAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken)
{
async Task IUsersService.ChangeAccountAuthDataAsync(ChangeUserAuthDataRequest request, CancellationToken cancellationToken)
{
var user = await users.GetByIdAsync(request.UserId, cancellationToken);
user.Email = request.NewAuthData.Email;
@@ -23,12 +23,11 @@ namespace ApplicationLayer.Services.Users
await unitOfWork.SaveAsync(cancellationToken);
}
async Task IUsersService.RemoveUserAccount(Guid userId, CancellationToken cancellationToken)
{
async Task IUsersService.RemoveUserAccount(Guid userId, CancellationToken cancellationToken)
{
var user = await users.GetByIdAsync(userId, cancellationToken);
users.Remove(user);
await unitOfWork.SaveAsync(cancellationToken);
}
}
}

View File

@@ -1,6 +1,5 @@
using ApplicationLayer.GeneralExceptions;
namespace ApplicationLayer.Services.VisaApplications.Exceptions
{
public class ApplicationAlreadyProcessedException() : ApiException("This application already processed or closed by applicant.");
}
namespace ApplicationLayer.Services.VisaApplications.Exceptions;
public class ApplicationAlreadyProcessedException() : ApiException("This application already processed or closed by applicant.");

View File

@@ -1,8 +1,7 @@
namespace ApplicationLayer.Services.VisaApplications.Models
namespace ApplicationLayer.Services.VisaApplications.Models;
public enum AuthorityRequestStatuses
{
public enum AuthorityRequestStatuses
{
Approved,
Rejected
}
Approved,
Rejected
}

View File

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

View File

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

View File

@@ -2,12 +2,12 @@
using Domains.VisaApplicationDomain;
using FluentValidation;
namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
namespace ApplicationLayer.Services.VisaApplications.Requests.Validation;
public class PastVisaValidator : AbstractValidator<PastVisa>
{
public class PastVisaValidator : AbstractValidator<PastVisa>
public PastVisaValidator(IDateTimeProvider dateTimeProvider)
{
public PastVisaValidator(IDateTimeProvider dateTimeProvider)
{
RuleFor(v => v.ExpirationDate)
.NotEmpty()
.WithMessage("Expiration date of past visa can not be empty")
@@ -24,5 +24,4 @@ namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
.NotEmpty()
.WithMessage("Name of past visa can not be empty");
}
}
}

View File

@@ -3,12 +3,12 @@ using Domains;
using Domains.VisaApplicationDomain;
using FluentValidation;
namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
namespace ApplicationLayer.Services.VisaApplications.Requests.Validation;
public class PastVisitValidator : AbstractValidator<PastVisit>
{
public class PastVisitValidator : AbstractValidator<PastVisit>
public PastVisitValidator(IDateTimeProvider dateTimeProvider)
{
public PastVisitValidator(IDateTimeProvider dateTimeProvider)
{
RuleFor(v => v.StartDate)
.NotEmpty()
.WithMessage("Start date of past visit can not be empty")
@@ -27,5 +27,4 @@ namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
.MaximumLength(ConfigurationConstraints.CountryNameLength)
.WithMessage($"Destination Country of past visit length must be less than {ConfigurationConstraints.CountryNameLength}");
}
}
}

View File

@@ -3,12 +3,12 @@ using Domains;
using Domains.VisaApplicationDomain;
using FluentValidation;
namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
namespace ApplicationLayer.Services.VisaApplications.Requests.Validation;
public class PermissionToDestCountryValidator : AbstractValidator<PermissionToDestCountry?>
{
public class PermissionToDestCountryValidator : AbstractValidator<PermissionToDestCountry?>
public PermissionToDestCountryValidator(IDateTimeProvider dateTimeProvider)
{
public PermissionToDestCountryValidator(IDateTimeProvider dateTimeProvider)
{
RuleFor(p => p!.ExpirationDate)
.NotEmpty()
.WithMessage("Expiration date of permission to destination Country can not be empty")
@@ -21,5 +21,4 @@ namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
.MaximumLength(ConfigurationConstraints.IssuerNameLength)
.WithMessage($"Issuer of permission to destination Country length must be less than {ConfigurationConstraints.IssuerNameLength}");
}
}
}

View File

@@ -3,12 +3,12 @@ using Domains;
using Domains.VisaApplicationDomain;
using FluentValidation;
namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
namespace ApplicationLayer.Services.VisaApplications.Requests.Validation;
public class ReentryPermitValidator : AbstractValidator<ReentryPermit?>
{
public class ReentryPermitValidator : AbstractValidator<ReentryPermit?>
public ReentryPermitValidator(IDateTimeProvider dateTimeProvider)
{
public ReentryPermitValidator(IDateTimeProvider dateTimeProvider)
{
RuleFor(p => p!.Number)
.NotEmpty()
.WithMessage("Re-entry permit number can not be empty")
@@ -21,5 +21,4 @@ namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
.GreaterThan(dateTimeProvider.Now())
.WithMessage("Re-entry permit must not be expired");
}
}
}

View File

@@ -4,51 +4,50 @@ using Domains;
using Domains.VisaApplicationDomain;
using FluentValidation;
namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
namespace ApplicationLayer.Services.VisaApplications.Requests.Validation;
public class VisaApplicationCreateRequestValidator : AbstractValidator<VisaApplicationCreateRequest>
{
public class VisaApplicationCreateRequestValidator : AbstractValidator<VisaApplicationCreateRequest>
public VisaApplicationCreateRequestValidator(
IValidator<ReentryPermit?> reentryPermitValidator,
IValidator<PastVisa> pastVisaValidator,
IValidator<PermissionToDestCountry?> permissionToDestCountryValidator,
IValidator<PastVisit> pastVisitValidator,
IApplicantsRepository applicants,
IUserIdProvider userIdProvider)
{
public VisaApplicationCreateRequestValidator(
IValidator<ReentryPermit?> reentryPermitValidator,
IValidator<PastVisa> pastVisaValidator,
IValidator<PermissionToDestCountry?> permissionToDestCountryValidator,
IValidator<PastVisit> pastVisitValidator,
IApplicantsRepository applicants,
IUserIdProvider userIdProvider)
{
RuleFor(r => r.ReentryPermit)
.NotEmpty()
.WithMessage("Non-residents must provide re-entry permission")
.SetValidator(reentryPermitValidator)
.WhenAsync(async (r, ct) =>
await applicants.IsApplicantNonResidentByUserId(userIdProvider.GetUserId(), ct));
RuleFor(r => r.ReentryPermit)
.NotEmpty()
.WithMessage("Non-residents must provide re-entry permission")
.SetValidator(reentryPermitValidator)
.WhenAsync(async (r, ct) =>
await applicants.IsApplicantNonResidentByUserId(userIdProvider.GetUserId(), ct));
RuleFor(r => r.DestinationCountry)
.NotEmpty()
.WithMessage("Destination country can not be empty");
RuleFor(r => r.DestinationCountry)
.NotEmpty()
.WithMessage("Destination country can not be empty");
RuleFor(r => r.VisaCategory)
.IsInEnum();
RuleFor(r => r.VisaCategory)
.IsInEnum();
RuleFor(r => r.RequestedNumberOfEntries)
.IsInEnum();
RuleFor(r => r.RequestedNumberOfEntries)
.IsInEnum();
RuleFor(r => r.ValidDaysRequested)
.GreaterThan(0)
.WithMessage($"Valid days requested should be positive number and less than {ConfigurationConstraints.MaxValidDays}")
.LessThanOrEqualTo(ConfigurationConstraints.MaxValidDays)
.WithMessage($"Valid days requested must be less than or equal to {ConfigurationConstraints.MaxValidDays}");
RuleFor(r => r.ValidDaysRequested)
.GreaterThan(0)
.WithMessage($"Valid days requested should be positive number and less than {ConfigurationConstraints.MaxValidDays}")
.LessThanOrEqualTo(ConfigurationConstraints.MaxValidDays)
.WithMessage($"Valid days requested must be less than or equal to {ConfigurationConstraints.MaxValidDays}");
RuleForEach(r => r.PastVisas)
.SetValidator(pastVisaValidator);
RuleForEach(r => r.PastVisas)
.SetValidator(pastVisaValidator);
When(r => r.VisaCategory == VisaCategory.Transit,
() =>
RuleFor(r => r.PermissionToDestCountry)
.SetValidator(permissionToDestCountryValidator));
When(r => r.VisaCategory == VisaCategory.Transit,
() =>
RuleFor(r => r.PermissionToDestCountry)
.SetValidator(permissionToDestCountryValidator));
RuleForEach(r => r.PastVisits)
.SetValidator(pastVisitValidator);
}
RuleForEach(r => r.PastVisits)
.SetValidator(pastVisitValidator);
}
}

View File

@@ -1,24 +1,23 @@
namespace Domains
namespace Domains;
public static class ConfigurationConstraints
{
public static class ConfigurationConstraints
{
public const int CityNameLength = 70;
public const int CountryNameLength = 70;
public const int CitizenshipLength = 30;
public const int ReentryPermitNumberLength = 25;
public const int IssuerNameLength = 200;
public const int VisaNameLength = 70;
public const int StreetNameLength = 100;
public const int PlaceOfWorkNameLength = 200;
public const int NameLength = 50;
public const int BuildingNumberLength = 10;
public const int PassportNumberLength = 20;
public const int PhoneNumberLength = 13;
public const int PhoneNumberMinLength = 11;
public const int EmailLength = 254;
public const int PasswordLength = 50;
public const int ApplicantMinAge = 14;
public const int JobTitleLength = 50;
public const int MaxValidDays = 90;
}
public const int CityNameLength = 70;
public const int CountryNameLength = 70;
public const int CitizenshipLength = 30;
public const int ReentryPermitNumberLength = 25;
public const int IssuerNameLength = 200;
public const int VisaNameLength = 70;
public const int StreetNameLength = 100;
public const int PlaceOfWorkNameLength = 200;
public const int NameLength = 50;
public const int BuildingNumberLength = 10;
public const int PassportNumberLength = 20;
public const int PhoneNumberLength = 13;
public const int PhoneNumberMinLength = 11;
public const int EmailLength = 254;
public const int PasswordLength = 50;
public const int ApplicantMinAge = 14;
public const int JobTitleLength = 50;
public const int MaxValidDays = 90;
}

View File

@@ -1,13 +1,12 @@
namespace Domains.Users
namespace Domains.Users;
/// Role of <see cref="User"/>
public enum Role
{
/// Role of <see cref="User"/>
public enum Role
{
/// Requests visa applications
Applicant,
/// Approves or declines applications
ApprovingAuthority,
/// Manages approving authorities
Admin
}
/// Requests visa applications
Applicant,
/// Approves or declines applications
ApprovingAuthority,
/// Manages approving authorities
Admin
}

View File

@@ -1,14 +1,13 @@
namespace Domains.Users
namespace Domains.Users;
public class User : IEntity
{
public class User : IEntity
{
/// Unique Identifier of <see cref="User"/>
public Guid Id { get; private set; } = Guid.NewGuid();
/// Unique Identifier of <see cref="User"/>
public Guid Id { get; private set; } = Guid.NewGuid();
public Role Role { get; set; }
public Role Role { get; set; }
public string Email { get; set; } = null!;
public string Email { get; set; } = null!;
public string Password { get; set; } = null!;
}
public string Password { get; set; } = null!;
}

View File

@@ -1,12 +1,11 @@
namespace Domains.VisaApplicationDomain
namespace Domains.VisaApplicationDomain;
public enum ApplicationStatus
{
public enum ApplicationStatus
{
/// Waits for approve
Pending,
Approved,
Rejected,
/// Closed by applicant
Closed
}
/// Waits for approve
Pending,
Approved,
Rejected,
/// Closed by applicant
Closed
}

View File

@@ -3,12 +3,12 @@ using ApplicationLayer.InfrastructureServicesInterfaces;
using ApplicationLayer.Services.AuthServices.NeededServices;
using Microsoft.Extensions.DependencyInjection;
namespace Infrastructure.Auth
namespace Infrastructure.Auth;
public static class ServiceCollectionsExtensions
{
public static class ServiceCollectionsExtensions
public static IServiceCollection AddTokenGenerator(this IServiceCollection services, TokenGeneratorOptions options)
{
public static IServiceCollection AddTokenGenerator(this IServiceCollection services, TokenGeneratorOptions options)
{
services.AddSingleton<JwtSecurityTokenHandler>();
services.AddSingleton<ITokenGenerator, TokenGenerator>(provider =>
{
@@ -20,5 +20,4 @@ namespace Infrastructure.Auth
return services;
}
}
}

View File

@@ -4,27 +4,26 @@ using ApplicationLayer.InfrastructureServicesInterfaces;
using ApplicationLayer.Services.AuthServices.NeededServices;
using Domains.Users;
namespace Infrastructure.Auth
namespace Infrastructure.Auth;
public class TokenGenerator(TokenGeneratorOptions options, JwtSecurityTokenHandler tokenHandler, IDateTimeProvider dateTimeProvider)
: ITokenGenerator
{
public class TokenGenerator(TokenGeneratorOptions options, JwtSecurityTokenHandler tokenHandler, IDateTimeProvider dateTimeProvider)
: ITokenGenerator
public string CreateToken(User user)
{
public string CreateToken(User user)
var claims = new List<Claim>
{
var claims = new List<Claim>
{
new(ClaimTypes.Role, user.Role.ToString()),
new(ClaimTypes.NameIdentifier, user.Id.ToString())
};
new(ClaimTypes.Role, user.Role.ToString()),
new(ClaimTypes.NameIdentifier, user.Id.ToString())
};
var token = new JwtSecurityToken(
issuer: options.Issuer,
audience: options.Audience,
expires: dateTimeProvider.Now().Add(options.ValidTime),
signingCredentials: options.Credentials,
claims: claims);
var token = new JwtSecurityToken(
issuer: options.Issuer,
audience: options.Audience,
expires: dateTimeProvider.Now().Add(options.ValidTime),
signingCredentials: options.Credentials,
claims: claims);
return tokenHandler.WriteToken(token);
}
return tokenHandler.WriteToken(token);
}
}

View File

@@ -1,6 +1,5 @@
using Microsoft.IdentityModel.Tokens;
namespace Infrastructure.Auth
{
public record TokenGeneratorOptions(string Issuer, string Audience, TimeSpan ValidTime, SigningCredentials Credentials);
}
namespace Infrastructure.Auth;
public record TokenGeneratorOptions(string Issuer, string Audience, TimeSpan ValidTime, SigningCredentials Credentials);

View File

@@ -3,12 +3,12 @@ using ApplicationLayer.Services.AuthServices.Requests;
using AutoMapper;
using Domains.ApplicantDomain;
namespace Infrastructure.Automapper.Profiles
namespace Infrastructure.Automapper.Profiles;
public class ApplicantProfile : Profile
{
public class ApplicantProfile : Profile
public ApplicantProfile()
{
public ApplicantProfile()
{
CreateMap<Applicant, ApplicantModel>(MemberList.Destination);
CreateMap<RegisterApplicantRequest, Applicant>(MemberList.Destination)
@@ -16,5 +16,4 @@ namespace Infrastructure.Automapper.Profiles
.ForMember(a => a.Name,
opts => opts.MapFrom(r => r.ApplicantName));
}
}
}

View File

@@ -2,15 +2,14 @@
using AutoMapper;
using Domains.ApplicantDomain;
namespace Infrastructure.Automapper.Profiles
namespace Infrastructure.Automapper.Profiles;
public class PlaceOfWorkProfile : Profile
{
public class PlaceOfWorkProfile : Profile
public PlaceOfWorkProfile()
{
public PlaceOfWorkProfile()
{
CreateMap<PlaceOfWorkModel, PlaceOfWork>(MemberList.Destination)
.ForMember(p => p.Id,
opts => opts.UseDestinationValue());
}
}
}

View File

@@ -2,15 +2,14 @@
using AutoMapper;
using Domains.Users;
namespace Infrastructure.Automapper.Profiles
namespace Infrastructure.Automapper.Profiles;
public class UserProfile : Profile
{
public class UserProfile : Profile
public UserProfile()
{
public UserProfile()
{
CreateMap<AuthData, User>(MemberList.Destination)
.ForMember(u => u.Role,
opts => opts.Ignore());
}
}
}

View File

@@ -3,12 +3,12 @@ using ApplicationLayer.Services.VisaApplications.Requests;
using AutoMapper;
using Domains.VisaApplicationDomain;
namespace Infrastructure.Automapper.Profiles
namespace Infrastructure.Automapper.Profiles;
public class VisaApplicationProfile : Profile
{
public class VisaApplicationProfile : Profile
public VisaApplicationProfile()
{
public VisaApplicationProfile()
{
CreateMap<VisaApplication, VisaApplicationModelForApplicant>(MemberList.Destination);
CreateMap<VisaApplication, VisaApplicationModelForAuthority>(MemberList.Destination)
@@ -21,5 +21,4 @@ namespace Infrastructure.Automapper.Profiles
.ForMember(va => va.ApplicantId,
opts => opts.Ignore());
}
}
}

View File

@@ -1,10 +1,9 @@
using ApplicationLayer.InfrastructureServicesInterfaces;
namespace Infrastructure.Common
namespace Infrastructure.Common;
/// Implements <see cref="IDateTimeProvider"/>
public class DateTimeProvider : IDateTimeProvider
{
/// Implements <see cref="IDateTimeProvider"/>
public class DateTimeProvider : IDateTimeProvider
{
DateTime IDateTimeProvider.Now() => DateTime.Now;
}
DateTime IDateTimeProvider.Now() => DateTime.Now;
}

View File

@@ -2,12 +2,12 @@
using ApplicationLayer.InfrastructureServicesInterfaces;
using Microsoft.AspNetCore.Http;
namespace Infrastructure.Common
namespace Infrastructure.Common;
public class UserIdProvider(IHttpContextAccessor contextAccessor) : IUserIdProvider
{
public class UserIdProvider(IHttpContextAccessor contextAccessor) : IUserIdProvider
Guid IUserIdProvider.GetUserId()
{
Guid IUserIdProvider.GetUserId()
{
var claim = contextAccessor.HttpContext!.User.Claims.SingleOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier);
if (claim is null)
{
@@ -15,5 +15,4 @@ namespace Infrastructure.Common
}
return Guid.Parse(claim.Value);
}
}
}

View File

@@ -1,6 +1,5 @@
using ApplicationLayer.Services.GeneralExceptions;
namespace Infrastructure.Database.Applicants.Repositories.Exceptions
{
public class ApplicantNotFoundByUserIdException() : EntityNotFoundException("Applicant not found.");
}
namespace Infrastructure.Database.Applicants.Repositories.Exceptions;
public class ApplicantNotFoundByUserIdException() : EntityNotFoundException("Applicant not found.");

View File

@@ -3,12 +3,12 @@ using Domains.Users;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.Users.Configuration
namespace Infrastructure.Database.Users.Configuration;
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public class UserConfiguration : IEntityTypeConfiguration<User>
public void Configure(EntityTypeBuilder<User> entity)
{
public void Configure(EntityTypeBuilder<User> entity)
{
entity.Property(u => u.Email)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.EmailLength);
@@ -19,5 +19,4 @@ namespace Infrastructure.Database.Users.Configuration
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.PasswordLength);
}
}
}

View File

@@ -3,20 +3,19 @@ using Domains.Users;
using Infrastructure.Database.Generic;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Database.Users.Repositories
namespace Infrastructure.Database.Users.Repositories;
/// <inheritdoc cref="IUsersRepository"/>
public class UsersRepository(IGenericReader reader, IGenericWriter writer)
: GenericRepository<User>(reader, writer), IUsersRepository
{
/// <inheritdoc cref="IUsersRepository"/>
public class UsersRepository(IGenericReader reader, IGenericWriter writer)
: GenericRepository<User>(reader, writer), IUsersRepository
async Task<User?> IUsersRepository.FindByEmailAsync(string email, CancellationToken cancellationToken)
{
async Task<User?> IUsersRepository.FindByEmailAsync(string email, CancellationToken cancellationToken)
{
return await LoadDomain().SingleOrDefaultAsync(u => u.Email == email, cancellationToken);
}
async Task<List<User>> IUsersRepository.GetAllOfRoleAsync(Role role, CancellationToken cancellationToken)
{
async Task<List<User>> IUsersRepository.GetAllOfRoleAsync(Role role, CancellationToken cancellationToken)
{
return await LoadDomain().Where(u => u.Role == role).ToListAsync(cancellationToken);
}
}
}

View File

@@ -2,15 +2,14 @@
using Domains.VisaApplicationDomain;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Infrastructure.Database.VisaApplications.Configuration
namespace Infrastructure.Database.VisaApplications.Configuration;
public static class PastVisitConfiguration<T> where T : class, IEntity
{
public static class PastVisitConfiguration<T> where T : class, IEntity
public static void Configure(OwnedNavigationBuilder<T, PastVisit> entity)
{
public static void Configure(OwnedNavigationBuilder<T, PastVisit> entity)
{
entity.Property(pv => pv.DestinationCountry)
.IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.CountryNameLength);
}
}
}

View File

@@ -1,7 +1,6 @@
using ApplicationLayer.Services.GeneralExceptions;
namespace Infrastructure.Database.VisaApplications.Repositories.Exceptions
{
public class ApplicationNotFoundByApplicantAndApplicationIdException(Guid applicationId)
: EntityNotFoundException($"Application with id {applicationId} not found for authenticated user");
}
namespace Infrastructure.Database.VisaApplications.Repositories.Exceptions;
public class ApplicationNotFoundByApplicantAndApplicationIdException(Guid applicationId)
: EntityNotFoundException($"Application with id {applicationId} not found for authenticated user");

View File

@@ -2,13 +2,13 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace SchengenVisaApi.Common
namespace SchengenVisaApi.Common;
/// Adds auth for swagger
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
/// Adds auth for swagger
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
void IConfigureOptions<SwaggerGenOptions>.Configure(SwaggerGenOptions options)
{
void IConfigureOptions<SwaggerGenOptions>.Configure(SwaggerGenOptions options)
{
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
@@ -34,5 +34,4 @@ namespace SchengenVisaApi.Common
}
});
}
}
}

View File

@@ -1,12 +1,9 @@
namespace SchengenVisaApi.Common
{
namespace SchengenVisaApi.Common;
#pragma warning disable CS1591
public static class PolicyConstants
{
public const string AdminPolicy = "AdminPolicy";
public const string ApplicantPolicy = "ApplicantPolicy";
public const string ApprovingAuthorityPolicy = "ApprovingAuthorityPolicy";
}
#pragma warning enable CS1591
public static class PolicyConstants
{
public const string AdminPolicy = "AdminPolicy";
public const string ApplicantPolicy = "ApplicantPolicy";
public const string ApprovingAuthorityPolicy = "ApprovingAuthorityPolicy";
}
#pragma warning enable CS1591

View File

@@ -10,106 +10,105 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SchengenVisaApi.Common;
namespace SchengenVisaApi.Controllers
namespace SchengenVisaApi.Controllers;
///<summary> Controller for user-auth and registration </summary>
[ApiController]
[Route("users")]
public class UsersController(
IRegisterService registerService,
ILoginService loginService,
IUsersService usersService,
IValidator<RegisterApplicantRequest> registerApplicantRequestValidator,
IValidator<AuthData> authDataValidator) : ControllerBase
{
///<summary> Controller for user-auth and registration </summary>
[ApiController]
[Route("users")]
public class UsersController(
IRegisterService registerService,
ILoginService loginService,
IUsersService usersService,
IValidator<RegisterApplicantRequest> registerApplicantRequestValidator,
IValidator<AuthData> authDataValidator) : ControllerBase
/// <summary> Adds applicant with user account to DB </summary>
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Route("register")]
public async Task<IActionResult> Register(RegisterApplicantRequest request, CancellationToken cancellationToken)
{
/// <summary> Adds applicant with user account to DB </summary>
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Route("register")]
public async Task<IActionResult> Register(RegisterApplicantRequest request, CancellationToken cancellationToken)
{
await registerApplicantRequestValidator.ValidateAndThrowAsync(request, cancellationToken);
await registerApplicantRequestValidator.ValidateAndThrowAsync(request, cancellationToken);
await registerService.RegisterApplicant(request, cancellationToken);
return Ok();
}
await registerService.RegisterApplicant(request, cancellationToken);
return Ok();
}
/// <summary> Adds approving authority with user account to DB </summary>
///<remarks> Accessible only for admins </remarks>
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Route("authorities")]
[Authorize(policy: PolicyConstants.AdminPolicy)]
public async Task<IActionResult> RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken)
{
await authDataValidator.ValidateAndThrowAsync(request.AuthData, cancellationToken);
/// <summary> Adds approving authority with user account to DB </summary>
///<remarks> Accessible only for admins </remarks>
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Route("authorities")]
[Authorize(policy: PolicyConstants.AdminPolicy)]
public async Task<IActionResult> RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken)
{
await authDataValidator.ValidateAndThrowAsync(request.AuthData, cancellationToken);
await registerService.RegisterAuthority(request, cancellationToken);
return Ok();
}
await registerService.RegisterAuthority(request, cancellationToken);
return Ok();
}
/// <summary> Returns JWT-token for authentication </summary>
[HttpGet]
[ProducesResponseType<string>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[Route("login")]
public async Task<IActionResult> Login(string email, string password, CancellationToken cancellationToken)
{
var result = await loginService.LoginAsync(email, password, cancellationToken);
return Ok(result);
}
/// <summary> Returns JWT-token for authentication </summary>
[HttpGet]
[ProducesResponseType<string>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[Route("login")]
public async Task<IActionResult> Login(string email, string password, CancellationToken cancellationToken)
{
var result = await loginService.LoginAsync(email, password, cancellationToken);
return Ok(result);
}
/// <summary> Returns list of authority accounts </summary>
/// <remarks> Accessible only for admins </remarks>
[HttpGet]
[ProducesResponseType<List<User>>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Route("authorities")]
[Authorize(policy: PolicyConstants.AdminPolicy)]
public async Task<IActionResult> GetAuthorityAccounts(CancellationToken cancellationToken)
{
var result = await usersService.GetAuthoritiesAccountsAsync(cancellationToken);
return Ok(result);
}
/// <summary> Returns list of authority accounts </summary>
/// <remarks> Accessible only for admins </remarks>
[HttpGet]
[ProducesResponseType<List<User>>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Route("authorities")]
[Authorize(policy: PolicyConstants.AdminPolicy)]
public async Task<IActionResult> GetAuthorityAccounts(CancellationToken cancellationToken)
{
var result = await usersService.GetAuthoritiesAccountsAsync(cancellationToken);
return Ok(result);
}
/// <summary> Changes authority's account authentication data </summary>
/// <remarks> Accessible only for admins </remarks>
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Route("authorities/{authorityAccountId:guid}")]
[Authorize(policy: PolicyConstants.AdminPolicy)]
public async Task<IActionResult> ChangeAuthorityAuthData(Guid authorityAccountId, AuthData authData, CancellationToken cancellationToken)
{
await authDataValidator.ValidateAndThrowAsync(authData, cancellationToken);
/// <summary> Changes authority's account authentication data </summary>
/// <remarks> Accessible only for admins </remarks>
[HttpPut]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Route("authorities/{authorityAccountId:guid}")]
[Authorize(policy: PolicyConstants.AdminPolicy)]
public async Task<IActionResult> ChangeAuthorityAuthData(Guid authorityAccountId, AuthData authData, CancellationToken cancellationToken)
{
await authDataValidator.ValidateAndThrowAsync(authData, cancellationToken);
await usersService.ChangeAccountAuthDataAsync(new ChangeUserAuthDataRequest(authorityAccountId, authData), cancellationToken);
return Ok();
}
await usersService.ChangeAccountAuthDataAsync(new ChangeUserAuthDataRequest(authorityAccountId, authData), cancellationToken);
return Ok();
}
/// <summary> Removes authority's account authentication data </summary>
/// <remarks> Accessible only for admins </remarks>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Route("authorities/{authorityAccountId:guid}")]
[Authorize(policy: PolicyConstants.AdminPolicy)]
public async Task<IActionResult> RemoveAuthorityAccount(Guid authorityAccountId, CancellationToken cancellationToken)
{
await usersService.RemoveUserAccount(authorityAccountId, cancellationToken);
return Ok();
}
/// <summary> Removes authority's account authentication data </summary>
/// <remarks> Accessible only for admins </remarks>
[HttpDelete]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Route("authorities/{authorityAccountId:guid}")]
[Authorize(policy: PolicyConstants.AdminPolicy)]
public async Task<IActionResult> RemoveAuthorityAccount(Guid authorityAccountId, CancellationToken cancellationToken)
{
await usersService.RemoveUserAccount(authorityAccountId, cancellationToken);
return Ok();
}
}

View File

@@ -6,67 +6,66 @@ using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace SchengenVisaApi.ExceptionFilters
namespace SchengenVisaApi.ExceptionFilters;
/// Handles <see cref="ApiException"/>
public class GlobalExceptionsFilter : IAsyncExceptionFilter
{
/// Handles <see cref="ApiException"/>
public class GlobalExceptionsFilter : IAsyncExceptionFilter
/// <inheritdoc cref="IExceptionFilter.OnException"/>
public async Task OnExceptionAsync(ExceptionContext context)
{
/// <inheritdoc cref="IExceptionFilter.OnException"/>
public async Task OnExceptionAsync(ExceptionContext context)
var exception = context.Exception;
var problemDetails = new ProblemDetails();
switch (exception)
{
var exception = context.Exception;
var problemDetails = new ProblemDetails();
case ValidationException validationException:
problemDetails.Extensions.Add("Errors", validationException.Errors.Select(e => e.ErrorMessage));
problemDetails.Detail = "Validation errors occured";
problemDetails.Status = StatusCodes.Status400BadRequest;
problemDetails.Title = "Bad request";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1";
break;
case ApiException:
problemDetails.Detail = exception.Message;
switch (exception)
{
case EntityNotFoundException:
problemDetails.Status = StatusCodes.Status404NotFound;
problemDetails.Title = "Requested entity not found";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4";
break;
case IncorrectLoginDataException:
problemDetails.Status = StatusCodes.Status403Forbidden;
problemDetails.Title = "Auth failed";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.3";
break;
case AlreadyExistsException:
problemDetails.Status = StatusCodes.Status409Conflict;
problemDetails.Title = "Already exists";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.8";
break;
case ApplicationAlreadyProcessedException:
problemDetails.Status = StatusCodes.Status409Conflict;
problemDetails.Title = "Already processed";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.8";
break;
default:
problemDetails.Status = StatusCodes.Status400BadRequest;
problemDetails.Title = "Bad request";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1";
break;
}
switch (exception)
{
case ValidationException validationException:
problemDetails.Extensions.Add("Errors", validationException.Errors.Select(e => e.ErrorMessage));
problemDetails.Detail = "Validation errors occured";
problemDetails.Status = StatusCodes.Status400BadRequest;
problemDetails.Title = "Bad request";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1";
break;
case ApiException:
problemDetails.Detail = exception.Message;
switch (exception)
{
case EntityNotFoundException:
problemDetails.Status = StatusCodes.Status404NotFound;
problemDetails.Title = "Requested entity not found";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4";
break;
case IncorrectLoginDataException:
problemDetails.Status = StatusCodes.Status403Forbidden;
problemDetails.Title = "Auth failed";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.3";
break;
case AlreadyExistsException:
problemDetails.Status = StatusCodes.Status409Conflict;
problemDetails.Title = "Already exists";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.8";
break;
case ApplicationAlreadyProcessedException:
problemDetails.Status = StatusCodes.Status409Conflict;
problemDetails.Title = "Already processed";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.8";
break;
default:
problemDetails.Status = StatusCodes.Status400BadRequest;
problemDetails.Title = "Bad request";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1";
break;
}
break;
default:
problemDetails.Status = StatusCodes.Status500InternalServerError;
problemDetails.Title = "An unhandled error occured";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1";
break;
}
await Results.Problem(problemDetails).ExecuteAsync(context.HttpContext);
context.ExceptionHandled = true;
break;
default:
problemDetails.Status = StatusCodes.Status500InternalServerError;
problemDetails.Title = "An unhandled error occured";
problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1";
break;
}
await Results.Problem(problemDetails).ExecuteAsync(context.HttpContext);
context.ExceptionHandled = true;
}
}