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 namespace ApplicationLayer.GeneralExceptions;
{
public class AlreadyExistsException(string message) : ApiException(message); public class AlreadyExistsException(string message) : ApiException(message);
}

View File

@@ -1,4 +1,3 @@
namespace ApplicationLayer.GeneralExceptions namespace ApplicationLayer.GeneralExceptions;
{
public class ApiException(string message) : Exception(message); 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; 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"/> /// <inheritdoc cref="Applicant.Name"/>
public class ApplicantModel public Name Name { get; set; } = null!;
{
/// <inheritdoc cref="Applicant.Name"/>
public Name Name { get; set; } = null!;
/// <inheritdoc cref="Applicant.Passport"/> /// <inheritdoc cref="Applicant.Passport"/>
public Passport Passport { get; set; } = null!; public Passport Passport { get; set; } = null!;
/// <inheritdoc cref="Applicant.BirthDate"/> /// <inheritdoc cref="Applicant.BirthDate"/>
public DateTime BirthDate { get; set; } public DateTime BirthDate { get; set; }
/// <inheritdoc cref="Applicant.CountryOfBirth"/> /// <inheritdoc cref="Applicant.CountryOfBirth"/>
public string CountryOfBirth { get; set; } = null!; public string CountryOfBirth { get; set; } = null!;
/// <inheritdoc cref="Applicant.CityOfBirth"/> /// <inheritdoc cref="Applicant.CityOfBirth"/>
public string CityOfBirth { get; set; } = null!; public string CityOfBirth { get; set; } = null!;
/// <inheritdoc cref="Applicant.Citizenship"/> /// <inheritdoc cref="Applicant.Citizenship"/>
public string Citizenship { get; set; } = null!; public string Citizenship { get; set; } = null!;
/// <inheritdoc cref="Applicant.CitizenshipByBirth"/> /// <inheritdoc cref="Applicant.CitizenshipByBirth"/>
public string CitizenshipByBirth { get; set; } = null!; public string CitizenshipByBirth { get; set; } = null!;
/// <inheritdoc cref="Applicant.Gender"/> /// <inheritdoc cref="Applicant.Gender"/>
public Gender Gender { get; set; } public Gender Gender { get; set; }
/// <inheritdoc cref="Applicant.MaritalStatus"/> /// <inheritdoc cref="Applicant.MaritalStatus"/>
public MaritalStatus MaritalStatus { get; set; } public MaritalStatus MaritalStatus { get; set; }
/// <inheritdoc cref="Applicant.FatherName"/> /// <inheritdoc cref="Applicant.FatherName"/>
public Name FatherName { get; set; } = null!; public Name FatherName { get; set; } = null!;
/// <inheritdoc cref="Applicant.MotherName"/> /// <inheritdoc cref="Applicant.MotherName"/>
public Name MotherName { get; set; } = null!; public Name MotherName { get; set; } = null!;
/// <inheritdoc cref="Applicant.JobTitle"/> /// <inheritdoc cref="Applicant.JobTitle"/>
public string JobTitle { get; set; } = null!; public string JobTitle { get; set; } = null!;
/// <inheritdoc cref="Applicant.PlaceOfWork"/> /// <inheritdoc cref="Applicant.PlaceOfWork"/>
public PlaceOfWork PlaceOfWork { get; set; } = null!; public PlaceOfWork PlaceOfWork { get; set; } = null!;
/// <inheritdoc cref="Applicant.IsNonResident"/> /// <inheritdoc cref="Applicant.IsNonResident"/>
public bool IsNonResident { get; set; } public bool IsNonResident { get; set; }
} }
}

View File

@@ -1,16 +1,15 @@
using Domains.ApplicantDomain; 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 /// Address of hirer
public Address Address { get; set; } = null!; public Address Address { get; set; } = null!;
/// Phone number of hirer /// Phone number of hirer
public string PhoneNum { get; set; } = null!; public string PhoneNum { get; set; } = null!;
} }
}

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
using ApplicationLayer.GeneralExceptions; using ApplicationLayer.GeneralExceptions;
namespace ApplicationLayer.Services.AuthServices.LoginService.Exceptions namespace ApplicationLayer.Services.AuthServices.LoginService.Exceptions;
{
public class IncorrectLoginDataException() : ApiException("Incorrect email or password"); 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 /// Handle login request
public interface ILoginService /// <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.LoginService.Exceptions;
using ApplicationLayer.Services.AuthServices.NeededServices; 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"/> async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken)
public class LoginService(IUsersRepository users, ITokenGenerator tokenGenerator) : ILoginService
{ {
async Task<string> ILoginService.LoginAsync(string email, string password, CancellationToken cancellationToken)
{
var user = await users.FindByEmailAsync(email, cancellationToken); var user = await users.FindByEmailAsync(email, cancellationToken);
if (user is null || user.Password != password) if (user is null || user.Password != password)
{ {
@@ -16,5 +16,4 @@ namespace ApplicationLayer.Services.AuthServices.LoginService
return tokenGenerator.CreateToken(user); return tokenGenerator.CreateToken(user);
} }
} }
}

View File

@@ -1,9 +1,8 @@
using Domains.Users; 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 ApplicationLayer.InfrastructureServicesInterfaces;
using Domains.Users; using Domains.Users;
namespace ApplicationLayer.Services.AuthServices.NeededServices 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);
/// Returns all accounts with specific role /// Repository pattern for <see cref="User"/>
/// <param name="role">role</param> public interface IUsersRepository : IGenericRepository<User>
/// <param name="cancellationToken">cancellation token</param> {
/// <returns>list of accounts</returns> /// Find <see cref="User"/> by email
Task<List<User>> GetAllOfRoleAsync(Role role, CancellationToken cancellationToken); /// <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; using ApplicationLayer.Services.AuthServices.Requests;
namespace ApplicationLayer.Services.AuthServices.RegisterService namespace ApplicationLayer.Services.AuthServices.RegisterService;
{
/// 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 /// Handles register request
Task RegisterAuthority(RegisterRequest request, CancellationToken cancellationToken); 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.ApplicantDomain;
using Domains.Users; 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"/> async Task IRegisterService.RegisterApplicant(RegisterApplicantRequest request, CancellationToken cancellationToken)
public class RegisterService(
IUsersRepository users,
IApplicantsRepository applicants,
IUnitOfWork unitOfWork,
IMapper mapper) : IRegisterService
{ {
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); var applicant = mapper.Map<Applicant>(request);
applicant.UserId = user.Id; applicant.UserId = user.Id;
await users.AddAsync(user, cancellationToken); await users.AddAsync(user, cancellationToken);
await applicants.AddAsync(applicant, 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;
await users.AddAsync(user, cancellationToken);
await unitOfWork.SaveAsync(cancellationToken);
}
} }
}
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 unitOfWork.SaveAsync(cancellationToken);
}
}

View File

@@ -2,22 +2,21 @@
using ApplicationLayer.Services.AuthServices.Common; using ApplicationLayer.Services.AuthServices.Common;
using Domains.ApplicantDomain; using Domains.ApplicantDomain;
namespace ApplicationLayer.Services.AuthServices.Requests namespace ApplicationLayer.Services.AuthServices.Requests;
{
public record RegisterApplicantRequest( public record RegisterApplicantRequest(
AuthData AuthData, AuthData AuthData,
Name ApplicantName, Name ApplicantName,
Passport Passport, Passport Passport,
DateTime BirthDate, DateTime BirthDate,
string CityOfBirth, string CityOfBirth,
string CountryOfBirth, string CountryOfBirth,
string Citizenship, string Citizenship,
string CitizenshipByBirth, string CitizenshipByBirth,
Gender Gender, Gender Gender,
MaritalStatus MaritalStatus, MaritalStatus MaritalStatus,
Name FatherName, Name FatherName,
Name MotherName, Name MotherName,
string JobTitle, string JobTitle,
PlaceOfWorkModel PlaceOfWork, PlaceOfWorkModel PlaceOfWork,
bool IsNonResident) : RegisterRequest(AuthData); bool IsNonResident) : RegisterRequest(AuthData);
}

View File

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

View File

@@ -3,12 +3,12 @@ using ApplicationLayer.Services.AuthServices.NeededServices;
using Domains; using Domains;
using FluentValidation; 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) RuleFor(d => d.Email)
.NotEmpty() .NotEmpty()
.WithMessage("Email can not be empty") .WithMessage("Email can not be empty")
@@ -28,5 +28,4 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation
.MaximumLength(ConfigurationConstraints.PasswordLength) .MaximumLength(ConfigurationConstraints.PasswordLength)
.WithMessage($"Password length must be less than {ConfigurationConstraints.PasswordLength}"); .WithMessage($"Password length must be less than {ConfigurationConstraints.PasswordLength}");
} }
} }
}

View File

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

View File

@@ -3,12 +3,12 @@ using Domains;
using Domains.ApplicantDomain; using Domains.ApplicantDomain;
using FluentValidation; 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) RuleFor(r => r.Issuer)
.NotEmpty() .NotEmpty()
.WithMessage("Passport issuer can not be empty") .WithMessage("Passport issuer can not be empty")
@@ -33,5 +33,4 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation
.LessThanOrEqualTo(dateTimeProvider.Now()) .LessThanOrEqualTo(dateTimeProvider.Now())
.WithMessage("Passport issue date must be in past"); .WithMessage("Passport issue date must be in past");
} }
} }
}

View File

@@ -2,12 +2,12 @@
using Domains; using Domains;
using FluentValidation; 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) RuleFor(p => p.Name)
.NotEmpty() .NotEmpty()
.WithMessage("Place of work name can not be empty") .WithMessage("Place of work name can not be empty")
@@ -46,5 +46,4 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation
.MaximumLength(ConfigurationConstraints.CountryNameLength) .MaximumLength(ConfigurationConstraints.CountryNameLength)
.WithMessage($"Building of place of work length must be less than {ConfigurationConstraints.BuildingNumberLength}"); .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 Domains.ApplicantDomain;
using FluentValidation; 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) RuleFor(r => r.AuthData)
.SetValidator(authDataValidator); .SetValidator(authDataValidator);
@@ -74,5 +74,4 @@ namespace ApplicationLayer.Services.AuthServices.Requests.Validation
RuleFor(r => r.PlaceOfWork) RuleFor(r => r.PlaceOfWork)
.SetValidator(placeOfWorkModelValidator); .SetValidator(placeOfWorkModelValidator);
} }
} }
}

View File

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

View File

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

View File

@@ -3,17 +3,17 @@ using ApplicationLayer.Services.AuthServices.NeededServices;
using ApplicationLayer.Services.Users.Requests; using ApplicationLayer.Services.Users.Requests;
using Domains.Users; 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); 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); var user = await users.GetByIdAsync(request.UserId, cancellationToken);
user.Email = request.NewAuthData.Email; user.Email = request.NewAuthData.Email;
@@ -23,12 +23,11 @@ namespace ApplicationLayer.Services.Users
await unitOfWork.SaveAsync(cancellationToken); 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); var user = await users.GetByIdAsync(userId, cancellationToken);
users.Remove(user); users.Remove(user);
await unitOfWork.SaveAsync(cancellationToken); await unitOfWork.SaveAsync(cancellationToken);
} }
} }
}

View File

@@ -1,6 +1,5 @@
using ApplicationLayer.GeneralExceptions; using ApplicationLayer.GeneralExceptions;
namespace ApplicationLayer.Services.VisaApplications.Exceptions namespace ApplicationLayer.Services.VisaApplications.Exceptions;
{
public class ApplicationAlreadyProcessedException() : ApiException("This application already processed or closed by applicant."); 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; 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"/> /// <inheritdoc cref="VisaApplication.Id"/>
public class VisaApplicationModelForApplicant public Guid Id { get; set; }
{
/// <inheritdoc cref="VisaApplication.Id"/>
public Guid Id { get; set; }
/// <inheritdoc cref="VisaApplication.Status"/> /// <inheritdoc cref="VisaApplication.Status"/>
public ApplicationStatus Status { get; set; } public ApplicationStatus Status { get; set; }
/// <inheritdoc cref="VisaApplication.ReentryPermit"/> /// <inheritdoc cref="VisaApplication.ReentryPermit"/>
public ReentryPermit? ReentryPermit { get; set; } public ReentryPermit? ReentryPermit { get; set; }
/// <inheritdoc cref="VisaApplication.DestinationCountry"/> /// <inheritdoc cref="VisaApplication.DestinationCountry"/>
public string DestinationCountry { get; set; } = null!; public string DestinationCountry { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PastVisas"/> /// <inheritdoc cref="VisaApplication.PastVisas"/>
public List<PastVisa> PastVisas { get; set; } = null!; public List<PastVisa> PastVisas { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.PermissionToDestCountry"/> /// <inheritdoc cref="VisaApplication.PermissionToDestCountry"/>
public PermissionToDestCountry? PermissionToDestCountry { get; set; } public PermissionToDestCountry? PermissionToDestCountry { get; set; }
/// <inheritdoc cref="VisaApplication.PastVisits"/> /// <inheritdoc cref="VisaApplication.PastVisits"/>
public List<PastVisit> PastVisits { get; set; } = null!; public List<PastVisit> PastVisits { get; set; } = null!;
/// <inheritdoc cref="VisaApplication.VisaCategory"/> /// <inheritdoc cref="VisaApplication.VisaCategory"/>
public VisaCategory VisaCategory { get; set; } public VisaCategory VisaCategory { get; set; }
/// <inheritdoc cref="VisaApplication.ForGroup"/> /// <inheritdoc cref="VisaApplication.ForGroup"/>
public bool ForGroup { get; set; } public bool ForGroup { get; set; }
/// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries"/> /// <inheritdoc cref="VisaApplication.RequestedNumberOfEntries"/>
public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; } public RequestedNumberOfEntries RequestedNumberOfEntries { get; set; }
/// <inheritdoc cref="VisaApplication.RequestDate"/> /// <inheritdoc cref="VisaApplication.RequestDate"/>
public DateTime RequestDate { get; set; } public DateTime RequestDate { get; set; }
/// <inheritdoc cref="VisaApplication.ValidDaysRequested"/> /// <inheritdoc cref="VisaApplication.ValidDaysRequested"/>
public int ValidDaysRequested { get; set; } public int ValidDaysRequested { get; set; }
} }
}

View File

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

View File

@@ -2,12 +2,12 @@
using Domains.VisaApplicationDomain; using Domains.VisaApplicationDomain;
using FluentValidation; 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) RuleFor(v => v.ExpirationDate)
.NotEmpty() .NotEmpty()
.WithMessage("Expiration date of past visa can not be empty") .WithMessage("Expiration date of past visa can not be empty")
@@ -24,5 +24,4 @@ namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
.NotEmpty() .NotEmpty()
.WithMessage("Name of past visa can not be empty"); .WithMessage("Name of past visa can not be empty");
} }
} }
}

View File

@@ -3,12 +3,12 @@ using Domains;
using Domains.VisaApplicationDomain; using Domains.VisaApplicationDomain;
using FluentValidation; 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) RuleFor(v => v.StartDate)
.NotEmpty() .NotEmpty()
.WithMessage("Start date of past visit can not be empty") .WithMessage("Start date of past visit can not be empty")
@@ -27,5 +27,4 @@ namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
.MaximumLength(ConfigurationConstraints.CountryNameLength) .MaximumLength(ConfigurationConstraints.CountryNameLength)
.WithMessage($"Destination Country of past visit length must be less than {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 Domains.VisaApplicationDomain;
using FluentValidation; 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) RuleFor(p => p!.ExpirationDate)
.NotEmpty() .NotEmpty()
.WithMessage("Expiration date of permission to destination Country can not be empty") .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) .MaximumLength(ConfigurationConstraints.IssuerNameLength)
.WithMessage($"Issuer of permission to destination Country length must be less than {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 Domains.VisaApplicationDomain;
using FluentValidation; 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) RuleFor(p => p!.Number)
.NotEmpty() .NotEmpty()
.WithMessage("Re-entry permit number can not be empty") .WithMessage("Re-entry permit number can not be empty")
@@ -21,5 +21,4 @@ namespace ApplicationLayer.Services.VisaApplications.Requests.Validation
.GreaterThan(dateTimeProvider.Now()) .GreaterThan(dateTimeProvider.Now())
.WithMessage("Re-entry permit must not be expired"); .WithMessage("Re-entry permit must not be expired");
} }
} }
}

View File

@@ -4,51 +4,50 @@ using Domains;
using Domains.VisaApplicationDomain; using Domains.VisaApplicationDomain;
using FluentValidation; 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( RuleFor(r => r.ReentryPermit)
IValidator<ReentryPermit?> reentryPermitValidator, .NotEmpty()
IValidator<PastVisa> pastVisaValidator, .WithMessage("Non-residents must provide re-entry permission")
IValidator<PermissionToDestCountry?> permissionToDestCountryValidator, .SetValidator(reentryPermitValidator)
IValidator<PastVisit> pastVisitValidator, .WhenAsync(async (r, ct) =>
IApplicantsRepository applicants, await applicants.IsApplicantNonResidentByUserId(userIdProvider.GetUserId(), ct));
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.DestinationCountry) RuleFor(r => r.DestinationCountry)
.NotEmpty() .NotEmpty()
.WithMessage("Destination country can not be empty"); .WithMessage("Destination country can not be empty");
RuleFor(r => r.VisaCategory) RuleFor(r => r.VisaCategory)
.IsInEnum(); .IsInEnum();
RuleFor(r => r.RequestedNumberOfEntries) RuleFor(r => r.RequestedNumberOfEntries)
.IsInEnum(); .IsInEnum();
RuleFor(r => r.ValidDaysRequested) RuleFor(r => r.ValidDaysRequested)
.GreaterThan(0) .GreaterThan(0)
.WithMessage($"Valid days requested should be positive number and less than {ConfigurationConstraints.MaxValidDays}") .WithMessage($"Valid days requested should be positive number and less than {ConfigurationConstraints.MaxValidDays}")
.LessThanOrEqualTo(ConfigurationConstraints.MaxValidDays) .LessThanOrEqualTo(ConfigurationConstraints.MaxValidDays)
.WithMessage($"Valid days requested must be less than or equal to {ConfigurationConstraints.MaxValidDays}"); .WithMessage($"Valid days requested must be less than or equal to {ConfigurationConstraints.MaxValidDays}");
RuleForEach(r => r.PastVisas) RuleForEach(r => r.PastVisas)
.SetValidator(pastVisaValidator); .SetValidator(pastVisaValidator);
When(r => r.VisaCategory == VisaCategory.Transit, When(r => r.VisaCategory == VisaCategory.Transit,
() => () =>
RuleFor(r => r.PermissionToDestCountry) RuleFor(r => r.PermissionToDestCountry)
.SetValidator(permissionToDestCountryValidator)); .SetValidator(permissionToDestCountryValidator));
RuleForEach(r => r.PastVisits) RuleForEach(r => r.PastVisits)
.SetValidator(pastVisitValidator); .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 CityNameLength = 70; public const int CitizenshipLength = 30;
public const int CountryNameLength = 70; public const int ReentryPermitNumberLength = 25;
public const int CitizenshipLength = 30; public const int IssuerNameLength = 200;
public const int ReentryPermitNumberLength = 25; public const int VisaNameLength = 70;
public const int IssuerNameLength = 200; public const int StreetNameLength = 100;
public const int VisaNameLength = 70; public const int PlaceOfWorkNameLength = 200;
public const int StreetNameLength = 100; public const int NameLength = 50;
public const int PlaceOfWorkNameLength = 200; public const int BuildingNumberLength = 10;
public const int NameLength = 50; public const int PassportNumberLength = 20;
public const int BuildingNumberLength = 10; public const int PhoneNumberLength = 13;
public const int PassportNumberLength = 20; public const int PhoneNumberMinLength = 11;
public const int PhoneNumberLength = 13; public const int EmailLength = 254;
public const int PhoneNumberMinLength = 11; public const int PasswordLength = 50;
public const int EmailLength = 254; public const int ApplicantMinAge = 14;
public const int PasswordLength = 50; public const int JobTitleLength = 50;
public const int ApplicantMinAge = 14; public const int MaxValidDays = 90;
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"/> /// Requests visa applications
public enum Role Applicant,
{ /// Approves or declines applications
/// Requests visa applications ApprovingAuthority,
Applicant, /// Manages approving authorities
/// Approves or declines applications Admin
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,
/// Waits for approve Approved,
Pending, Rejected,
Approved, /// Closed by applicant
Rejected, Closed
/// Closed by applicant }
Closed
}
}

View File

@@ -3,12 +3,12 @@ using ApplicationLayer.InfrastructureServicesInterfaces;
using ApplicationLayer.Services.AuthServices.NeededServices; using ApplicationLayer.Services.AuthServices.NeededServices;
using Microsoft.Extensions.DependencyInjection; 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<JwtSecurityTokenHandler>();
services.AddSingleton<ITokenGenerator, TokenGenerator>(provider => services.AddSingleton<ITokenGenerator, TokenGenerator>(provider =>
{ {
@@ -20,5 +20,4 @@ namespace Infrastructure.Auth
return services; return services;
} }
} }
}

View File

@@ -4,27 +4,26 @@ using ApplicationLayer.InfrastructureServicesInterfaces;
using ApplicationLayer.Services.AuthServices.NeededServices; using ApplicationLayer.Services.AuthServices.NeededServices;
using Domains.Users; 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) public string CreateToken(User user)
: ITokenGenerator
{ {
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( var token = new JwtSecurityToken(
issuer: options.Issuer, issuer: options.Issuer,
audience: options.Audience, audience: options.Audience,
expires: dateTimeProvider.Now().Add(options.ValidTime), expires: dateTimeProvider.Now().Add(options.ValidTime),
signingCredentials: options.Credentials, signingCredentials: options.Credentials,
claims: claims); claims: claims);
return tokenHandler.WriteToken(token); return tokenHandler.WriteToken(token);
}
} }
} }

View File

@@ -1,6 +1,5 @@
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
namespace Infrastructure.Auth namespace Infrastructure.Auth;
{
public record TokenGeneratorOptions(string Issuer, string Audience, TimeSpan ValidTime, SigningCredentials Credentials); 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 AutoMapper;
using Domains.ApplicantDomain; 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<Applicant, ApplicantModel>(MemberList.Destination);
CreateMap<RegisterApplicantRequest, Applicant>(MemberList.Destination) CreateMap<RegisterApplicantRequest, Applicant>(MemberList.Destination)
@@ -16,5 +16,4 @@ namespace Infrastructure.Automapper.Profiles
.ForMember(a => a.Name, .ForMember(a => a.Name,
opts => opts.MapFrom(r => r.ApplicantName)); opts => opts.MapFrom(r => r.ApplicantName));
} }
} }
}

View File

@@ -2,15 +2,14 @@
using AutoMapper; using AutoMapper;
using Domains.ApplicantDomain; 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) CreateMap<PlaceOfWorkModel, PlaceOfWork>(MemberList.Destination)
.ForMember(p => p.Id, .ForMember(p => p.Id,
opts => opts.UseDestinationValue()); opts => opts.UseDestinationValue());
} }
} }
}

View File

@@ -2,15 +2,14 @@
using AutoMapper; using AutoMapper;
using Domains.Users; 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) CreateMap<AuthData, User>(MemberList.Destination)
.ForMember(u => u.Role, .ForMember(u => u.Role,
opts => opts.Ignore()); opts => opts.Ignore());
} }
} }
}

View File

@@ -3,12 +3,12 @@ using ApplicationLayer.Services.VisaApplications.Requests;
using AutoMapper; using AutoMapper;
using Domains.VisaApplicationDomain; 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, VisaApplicationModelForApplicant>(MemberList.Destination);
CreateMap<VisaApplication, VisaApplicationModelForAuthority>(MemberList.Destination) CreateMap<VisaApplication, VisaApplicationModelForAuthority>(MemberList.Destination)
@@ -21,5 +21,4 @@ namespace Infrastructure.Automapper.Profiles
.ForMember(va => va.ApplicantId, .ForMember(va => va.ApplicantId,
opts => opts.Ignore()); opts => opts.Ignore());
} }
} }
}

View File

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

View File

@@ -2,12 +2,12 @@
using ApplicationLayer.InfrastructureServicesInterfaces; using ApplicationLayer.InfrastructureServicesInterfaces;
using Microsoft.AspNetCore.Http; 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); var claim = contextAccessor.HttpContext!.User.Claims.SingleOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier);
if (claim is null) if (claim is null)
{ {
@@ -15,5 +15,4 @@ namespace Infrastructure.Common
} }
return Guid.Parse(claim.Value); return Guid.Parse(claim.Value);
} }
} }
}

View File

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

View File

@@ -3,12 +3,12 @@ using Domains.Users;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders; 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) entity.Property(u => u.Email)
.IsUnicode(false) .IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.EmailLength); .HasMaxLength(ConfigurationConstraints.EmailLength);
@@ -19,5 +19,4 @@ namespace Infrastructure.Database.Users.Configuration
.IsUnicode(false) .IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.PasswordLength); .HasMaxLength(ConfigurationConstraints.PasswordLength);
} }
} }
}

View File

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

View File

@@ -2,15 +2,14 @@
using Domains.VisaApplicationDomain; using Domains.VisaApplicationDomain;
using Microsoft.EntityFrameworkCore.Metadata.Builders; 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) entity.Property(pv => pv.DestinationCountry)
.IsUnicode(false) .IsUnicode(false)
.HasMaxLength(ConfigurationConstraints.CountryNameLength); .HasMaxLength(ConfigurationConstraints.CountryNameLength);
} }
} }
}

View File

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

View File

@@ -2,13 +2,13 @@
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerGen;
namespace SchengenVisaApi.Common namespace SchengenVisaApi.Common;
/// Adds auth for swagger
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{ {
/// Adds auth for swagger void IConfigureOptions<SwaggerGenOptions>.Configure(SwaggerGenOptions options)
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{ {
void IConfigureOptions<SwaggerGenOptions>.Configure(SwaggerGenOptions options)
{
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{ {
In = ParameterLocation.Header, 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 #pragma warning disable CS1591
public static class PolicyConstants public static class PolicyConstants
{ {
public const string AdminPolicy = "AdminPolicy"; public const string AdminPolicy = "AdminPolicy";
public const string ApplicantPolicy = "ApplicantPolicy"; public const string ApplicantPolicy = "ApplicantPolicy";
public const string ApprovingAuthorityPolicy = "ApprovingAuthorityPolicy"; public const string ApprovingAuthorityPolicy = "ApprovingAuthorityPolicy";
}
#pragma warning enable CS1591
} }
#pragma warning enable CS1591

View File

@@ -10,106 +10,105 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using SchengenVisaApi.Common; 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> /// <summary> Adds applicant with user account to DB </summary>
[ApiController] [HttpPost]
[Route("users")] [ProducesResponseType(StatusCodes.Status200OK)]
public class UsersController( [ProducesResponseType(StatusCodes.Status409Conflict)]
IRegisterService registerService, [ProducesResponseType(StatusCodes.Status400BadRequest)]
ILoginService loginService, [Route("register")]
IUsersService usersService, public async Task<IActionResult> Register(RegisterApplicantRequest request, CancellationToken cancellationToken)
IValidator<RegisterApplicantRequest> registerApplicantRequestValidator,
IValidator<AuthData> authDataValidator) : ControllerBase
{ {
/// <summary> Adds applicant with user account to DB </summary> await registerApplicantRequestValidator.ValidateAndThrowAsync(request, cancellationToken);
[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 registerService.RegisterApplicant(request, cancellationToken); await registerService.RegisterApplicant(request, cancellationToken);
return Ok(); 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);
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 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);
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> 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();
}
/// <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> 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();
}
/// <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;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
namespace SchengenVisaApi.ExceptionFilters namespace SchengenVisaApi.ExceptionFilters;
/// Handles <see cref="ApiException"/>
public class GlobalExceptionsFilter : IAsyncExceptionFilter
{ {
/// Handles <see cref="ApiException"/> /// <inheritdoc cref="IExceptionFilter.OnException"/>
public class GlobalExceptionsFilter : IAsyncExceptionFilter public async Task OnExceptionAsync(ExceptionContext context)
{ {
/// <inheritdoc cref="IExceptionFilter.OnException"/> var exception = context.Exception;
public async Task OnExceptionAsync(ExceptionContext context) var problemDetails = new ProblemDetails();
switch (exception)
{ {
var exception = context.Exception; case ValidationException validationException:
var problemDetails = new ProblemDetails(); 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) break;
{ default:
case ValidationException validationException: problemDetails.Status = StatusCodes.Status500InternalServerError;
problemDetails.Extensions.Add("Errors", validationException.Errors.Select(e => e.ErrorMessage)); problemDetails.Title = "An unhandled error occured";
problemDetails.Detail = "Validation errors occured"; problemDetails.Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1";
problemDetails.Status = StatusCodes.Status400BadRequest; break;
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;
} }
await Results.Problem(problemDetails).ExecuteAsync(context.HttpContext);
context.ExceptionHandled = true;
} }
} }