Compare commits
9 Commits
2d7d23d26b
...
120963f3cc
Author | SHA1 | Date | |
---|---|---|---|
120963f3cc | |||
4d1f6edc2e | |||
e5b3180220 | |||
6a9504d6ff | |||
68a9e06eeb | |||
41158b34c5 | |||
9ccd0bb68d | |||
bb309d7c20 | |||
7229a10ad5 |
@ -9,7 +9,7 @@ public static class CustomValidators
|
|||||||
{
|
{
|
||||||
return
|
return
|
||||||
ruleBuilder
|
ruleBuilder
|
||||||
.Matches(@"^[a-z0-9-_.]*$");
|
.Matches(@"^[a-z0-9-_\.]*$");
|
||||||
}
|
}
|
||||||
|
|
||||||
// According to RFC 5321.
|
// According to RFC 5321.
|
||||||
@ -18,7 +18,7 @@ public static class CustomValidators
|
|||||||
{
|
{
|
||||||
return
|
return
|
||||||
ruleBuilder
|
ruleBuilder
|
||||||
.Matches(@"^[\w\.-]{1,64}@[\w\.-]{1,251}\.\w{2,4}$");
|
.Matches(@"^[a-z0-9-_\.]{1,64}@[a-z0-9-_\.]{1,251}\.[a-z0-9-_]{2,4}$");
|
||||||
}
|
}
|
||||||
|
|
||||||
// According to ITU-T E.164, no spaces.
|
// According to ITU-T E.164, no spaces.
|
||||||
|
7
src/Application/Common/Services/EmailSenderService.cs
Normal file
7
src/Application/Common/Services/EmailSenderService.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
|
||||||
|
public interface EmailSenderService
|
||||||
|
{
|
||||||
|
Task SendAsync(string[] addresses, string subject, string body,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
}
|
@ -11,4 +11,11 @@ public record AddCompanyCommand : IRequest<CompanyDto>
|
|||||||
public string ContactEmail { get; set; }
|
public string ContactEmail { get; set; }
|
||||||
|
|
||||||
public string ContactPhoneNumber { get; set; }
|
public string ContactPhoneNumber { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
public string Password { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
|
||||||
using cuqmbr.TravelGuide.Domain.Entities;
|
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Companies.Commands.AddCompany;
|
namespace cuqmbr.TravelGuide.Application.Companies.Commands.AddCompany;
|
||||||
|
|
||||||
@ -11,13 +15,14 @@ public class AddCompanyCommandHandler :
|
|||||||
{
|
{
|
||||||
private readonly UnitOfWork _unitOfWork;
|
private readonly UnitOfWork _unitOfWork;
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
|
private readonly PasswordHasherService _passwordHasher;
|
||||||
|
|
||||||
public AddCompanyCommandHandler(
|
public AddCompanyCommandHandler(UnitOfWork unitOfWork, IMapper mapper,
|
||||||
UnitOfWork unitOfWork,
|
PasswordHasherService passwordHasher)
|
||||||
IMapper mapper)
|
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
|
_passwordHasher = passwordHasher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CompanyDto> Handle(
|
public async Task<CompanyDto> Handle(
|
||||||
@ -33,12 +38,51 @@ public class AddCompanyCommandHandler :
|
|||||||
"Company with given name already exists.");
|
"Company with given name already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Create new account for employee
|
||||||
|
|
||||||
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
|
e => e.Email == request.Email,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (account != null)
|
||||||
|
{
|
||||||
|
throw new DuplicateEntityException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var role = (await _unitOfWork.RoleRepository.GetPageAsync(
|
||||||
|
1, IdentityRole.Enumerations.Count(), cancellationToken))
|
||||||
|
.Items
|
||||||
|
.First(r => r.Value.Equals(IdentityRole.CompanyOwner));
|
||||||
|
|
||||||
|
var salt = RandomNumberGenerator.GetBytes(128 / 8);
|
||||||
|
var hash = await _passwordHasher.HashAsync(
|
||||||
|
Encoding.UTF8.GetBytes(request.Password),
|
||||||
|
salt, cancellationToken);
|
||||||
|
|
||||||
|
var saltBase64 = Convert.ToBase64String(salt);
|
||||||
|
var hashBase64 = Convert.ToBase64String(hash);
|
||||||
|
|
||||||
|
account = new Account()
|
||||||
|
{
|
||||||
|
Username = request.Username,
|
||||||
|
Email = request.Email,
|
||||||
|
PasswordHash = hashBase64,
|
||||||
|
PasswordSalt = saltBase64,
|
||||||
|
AccountRoles = new AccountRole[] { new() { RoleId = role.Id } }
|
||||||
|
};
|
||||||
|
|
||||||
|
account = await _unitOfWork.AccountRepository.AddOneAsync(
|
||||||
|
account, cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
entity = new Company()
|
entity = new Company()
|
||||||
{
|
{
|
||||||
Name = request.Name,
|
Name = request.Name,
|
||||||
LegalAddress = request.LegalAddress,
|
LegalAddress = request.LegalAddress,
|
||||||
ContactEmail = request.ContactEmail,
|
ContactEmail = request.ContactEmail,
|
||||||
ContactPhoneNumber = request.ContactPhoneNumber
|
ContactPhoneNumber = request.ContactPhoneNumber,
|
||||||
|
Account = account
|
||||||
};
|
};
|
||||||
|
|
||||||
entity = await _unitOfWork.CompanyRepository.AddOneAsync(
|
entity = await _unitOfWork.CompanyRepository.AddOneAsync(
|
||||||
|
@ -54,5 +54,46 @@ public class AddCompanyCommandValidator : AbstractValidator<AddCompanyCommand>
|
|||||||
cultureService.Culture,
|
cultureService.Culture,
|
||||||
localizer["FluentValidation.MaximumLength"],
|
localizer["FluentValidation.MaximumLength"],
|
||||||
64));
|
64));
|
||||||
|
|
||||||
|
|
||||||
|
RuleFor(v => v.Username)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MinimumLength(1)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MinimumLength"],
|
||||||
|
1))
|
||||||
|
.MaximumLength(32)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
32))
|
||||||
|
.IsUsername()
|
||||||
|
.WithMessage(localizer["FluentValidation.IsUsername"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.Email)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.IsEmail()
|
||||||
|
.WithMessage(localizer["FluentValidation.IsEmail"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.Password)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MinimumLength(8)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MinimumLength"],
|
||||||
|
8))
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ public class DeleteCompanyCommandHandler : IRequestHandler<DeleteCompanyCommand>
|
|||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var entity = await _unitOfWork.CompanyRepository.GetOneAsync(
|
var entity = await _unitOfWork.CompanyRepository.GetOneAsync(
|
||||||
e => e.Guid == request.Guid, cancellationToken);
|
e => e.Guid == request.Guid, e => e.Account, cancellationToken);
|
||||||
|
|
||||||
if (entity == null)
|
if (entity == null)
|
||||||
{
|
{
|
||||||
@ -28,6 +28,9 @@ public class DeleteCompanyCommandHandler : IRequestHandler<DeleteCompanyCommand>
|
|||||||
await _unitOfWork.CompanyRepository.DeleteOneAsync(
|
await _unitOfWork.CompanyRepository.DeleteOneAsync(
|
||||||
entity, cancellationToken);
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.AccountRepository.DeleteOneAsync(
|
||||||
|
entity.Account, cancellationToken);
|
||||||
|
|
||||||
await _unitOfWork.SaveAsync(cancellationToken);
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
_unitOfWork.Dispose();
|
_unitOfWork.Dispose();
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,14 @@ public class UpdateCompanyCommandHandler :
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
|
a => a.Id == entity.AccountId, cancellationToken);
|
||||||
|
|
||||||
entity.Name = request.Name;
|
entity.Name = request.Name;
|
||||||
entity.LegalAddress = request.LegalAddress;
|
entity.LegalAddress = request.LegalAddress;
|
||||||
entity.ContactEmail = request.ContactEmail;
|
entity.ContactEmail = request.ContactEmail;
|
||||||
entity.ContactPhoneNumber = request.ContactPhoneNumber;
|
entity.ContactPhoneNumber = request.ContactPhoneNumber;
|
||||||
|
entity.Account = account;
|
||||||
|
|
||||||
entity = await _unitOfWork.CompanyRepository.UpdateOneAsync(
|
entity = await _unitOfWork.CompanyRepository.UpdateOneAsync(
|
||||||
entity, cancellationToken);
|
entity, cancellationToken);
|
||||||
|
21
src/Application/Companies/CompanyAccountDto.cs
Normal file
21
src/Application/Companies/CompanyAccountDto.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Companies;
|
||||||
|
|
||||||
|
public sealed class CompanyAccountDto : IMapFrom<Account>
|
||||||
|
{
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
public void Mapping(MappingProfile profile)
|
||||||
|
{
|
||||||
|
profile.CreateMap<Account, CompanyAccountDto>()
|
||||||
|
.ForMember(
|
||||||
|
d => d.Uuid,
|
||||||
|
opt => opt.MapFrom(s => s.Guid));
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,8 @@ public sealed class CompanyDto : IMapFrom<Company>
|
|||||||
|
|
||||||
public string ContactPhoneNumber { get; set; }
|
public string ContactPhoneNumber { get; set; }
|
||||||
|
|
||||||
|
public CompanyAccountDto Account { get; set; }
|
||||||
|
|
||||||
public void Mapping(MappingProfile profile)
|
public void Mapping(MappingProfile profile)
|
||||||
{
|
{
|
||||||
profile.CreateMap<Company, CompanyDto>()
|
profile.CreateMap<Company, CompanyDto>()
|
||||||
|
@ -33,6 +33,19 @@ public class GetCompaniesPageQueryHandler :
|
|||||||
request.PageNumber, request.PageSize,
|
request.PageNumber, request.PageSize,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
// Hydrate companies
|
||||||
|
|
||||||
|
var accountIds = paginatedList.Items.Select(e => e.AccountId);
|
||||||
|
var accounts = await _unitOfWork.AccountRepository.GetPageAsync(
|
||||||
|
e => accountIds.Contains(e.Id),
|
||||||
|
1, paginatedList.Items.Count, cancellationToken);
|
||||||
|
|
||||||
|
foreach (var company in paginatedList.Items)
|
||||||
|
{
|
||||||
|
company.Account =
|
||||||
|
accounts.Items.First(a => a.Id == company.AccountId);
|
||||||
|
}
|
||||||
|
|
||||||
var mappedItems = _mapper
|
var mappedItems = _mapper
|
||||||
.ProjectTo<CompanyDto>(paginatedList.Items.AsQueryable());
|
.ProjectTo<CompanyDto>(paginatedList.Items.AsQueryable());
|
||||||
|
|
||||||
|
@ -26,13 +26,18 @@ public class GetCompanyQueryHandler :
|
|||||||
var entity = await _unitOfWork.CompanyRepository.GetOneAsync(
|
var entity = await _unitOfWork.CompanyRepository.GetOneAsync(
|
||||||
e => e.Guid == request.Guid, cancellationToken);
|
e => e.Guid == request.Guid, cancellationToken);
|
||||||
|
|
||||||
_unitOfWork.Dispose();
|
|
||||||
|
|
||||||
if (entity == null)
|
if (entity == null)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
|
e => e.Id == entity.AccountId, cancellationToken);
|
||||||
|
|
||||||
|
entity.Account = account;
|
||||||
|
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
return _mapper.Map<CompanyDto>(entity);
|
return _mapper.Map<CompanyDto>(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,4 +9,11 @@ public sealed class AddCompanyViewModel
|
|||||||
public string ContactEmail { get; set; }
|
public string ContactEmail { get; set; }
|
||||||
|
|
||||||
public string ContactPhoneNumber { get; set; }
|
public string ContactPhoneNumber { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
public string Password { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ namespace cuqmbr.TravelGuide.Application;
|
|||||||
|
|
||||||
public sealed class ConfigurationOptions
|
public sealed class ConfigurationOptions
|
||||||
{
|
{
|
||||||
public static string SectionName { get; } = "Application";
|
public static string SectionName { get; } = "";
|
||||||
|
|
||||||
public LocalizationConfigurationOptions Localization { get; set; } = new();
|
public LocalizationConfigurationOptions Localization { get; set; } = new();
|
||||||
|
|
||||||
|
@ -20,4 +20,11 @@ public record AddEmployeeCommand : IRequest<EmployeeDto>
|
|||||||
public Guid CompanyGuid { get; set; }
|
public Guid CompanyGuid { get; set; }
|
||||||
|
|
||||||
public ICollection<EmployeeDocumentModel> Documents { get; set; }
|
public ICollection<EmployeeDocumentModel> Documents { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
public string Password { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,10 @@ using cuqmbr.TravelGuide.Domain.Entities;
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Employees.Commands.AddEmployee;
|
namespace cuqmbr.TravelGuide.Application.Employees.Commands.AddEmployee;
|
||||||
|
|
||||||
@ -13,15 +17,15 @@ public class AddEmployeeCommandHandler :
|
|||||||
private readonly UnitOfWork _unitOfWork;
|
private readonly UnitOfWork _unitOfWork;
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
private readonly IStringLocalizer _localizer;
|
private readonly IStringLocalizer _localizer;
|
||||||
|
private readonly PasswordHasherService _passwordHasher;
|
||||||
|
|
||||||
public AddEmployeeCommandHandler(
|
public AddEmployeeCommandHandler(UnitOfWork unitOfWork, IMapper mapper,
|
||||||
UnitOfWork unitOfWork,
|
IStringLocalizer localizer, PasswordHasherService passwordHasher)
|
||||||
IMapper mapper,
|
|
||||||
IStringLocalizer localizer)
|
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
_localizer = localizer;
|
_localizer = localizer;
|
||||||
|
_passwordHasher = passwordHasher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmployeeDto> Handle(
|
public async Task<EmployeeDto> Handle(
|
||||||
@ -52,6 +56,44 @@ public class AddEmployeeCommandHandler :
|
|||||||
throw new DuplicateEntityException();
|
throw new DuplicateEntityException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Create new account for employee
|
||||||
|
|
||||||
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
|
e => e.Email == request.Email,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (account != null)
|
||||||
|
{
|
||||||
|
throw new DuplicateEntityException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var role = (await _unitOfWork.RoleRepository.GetPageAsync(
|
||||||
|
1, IdentityRole.Enumerations.Count(), cancellationToken))
|
||||||
|
.Items
|
||||||
|
.First(r => r.Value.Equals(IdentityRole.CompanyEmployee));
|
||||||
|
|
||||||
|
var salt = RandomNumberGenerator.GetBytes(128 / 8);
|
||||||
|
var hash = await _passwordHasher.HashAsync(
|
||||||
|
Encoding.UTF8.GetBytes(request.Password),
|
||||||
|
salt, cancellationToken);
|
||||||
|
|
||||||
|
var saltBase64 = Convert.ToBase64String(salt);
|
||||||
|
var hashBase64 = Convert.ToBase64String(hash);
|
||||||
|
|
||||||
|
account = new Account()
|
||||||
|
{
|
||||||
|
Username = request.Username,
|
||||||
|
Email = request.Email,
|
||||||
|
PasswordHash = hashBase64,
|
||||||
|
PasswordSalt = saltBase64,
|
||||||
|
AccountRoles = new AccountRole[] { new() { RoleId = role.Id } }
|
||||||
|
};
|
||||||
|
|
||||||
|
account = await _unitOfWork.AccountRepository.AddOneAsync(
|
||||||
|
account, cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
entity = new Employee()
|
entity = new Employee()
|
||||||
{
|
{
|
||||||
FirstName = request.FirstName,
|
FirstName = request.FirstName,
|
||||||
@ -66,12 +108,14 @@ public class AddEmployeeCommandHandler :
|
|||||||
Information = d.Information
|
Information = d.Information
|
||||||
})
|
})
|
||||||
.ToArray(),
|
.ToArray(),
|
||||||
Company = parentEntity
|
Company = parentEntity,
|
||||||
|
Account = account
|
||||||
};
|
};
|
||||||
|
|
||||||
entity = await _unitOfWork.EmployeeRepository.AddOneAsync(
|
entity = await _unitOfWork.EmployeeRepository.AddOneAsync(
|
||||||
entity, cancellationToken);
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
await _unitOfWork.SaveAsync(cancellationToken);
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
_unitOfWork.Dispose();
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.FluentValidation;
|
||||||
using cuqmbr.TravelGuide.Application.Common.Services;
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
using cuqmbr.TravelGuide.Domain.Enums;
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
@ -79,5 +80,46 @@ public class AddEmployeeCommandValidator : AbstractValidator<AddEmployeeCommand>
|
|||||||
localizer["FluentValidation.MaximumLength"],
|
localizer["FluentValidation.MaximumLength"],
|
||||||
256));
|
256));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
RuleFor(v => v.Username)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MinimumLength(1)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MinimumLength"],
|
||||||
|
1))
|
||||||
|
.MaximumLength(32)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
32))
|
||||||
|
.IsUsername()
|
||||||
|
.WithMessage(localizer["FluentValidation.IsUsername"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.Email)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.IsEmail()
|
||||||
|
.WithMessage(localizer["FluentValidation.IsEmail"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.Password)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MinimumLength(8)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MinimumLength"],
|
||||||
|
8))
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ public class DeleteEmployeeCommandHandler : IRequestHandler<DeleteEmployeeComman
|
|||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var entity = await _unitOfWork.EmployeeRepository.GetOneAsync(
|
var entity = await _unitOfWork.EmployeeRepository.GetOneAsync(
|
||||||
e => e.Guid == request.Guid, cancellationToken);
|
e => e.Guid == request.Guid, e => e.Account, cancellationToken);
|
||||||
|
|
||||||
if (entity == null)
|
if (entity == null)
|
||||||
{
|
{
|
||||||
@ -31,6 +31,9 @@ public class DeleteEmployeeCommandHandler : IRequestHandler<DeleteEmployeeComman
|
|||||||
await _unitOfWork.EmployeeRepository.DeleteOneAsync(
|
await _unitOfWork.EmployeeRepository.DeleteOneAsync(
|
||||||
entity, cancellationToken);
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.AccountRepository.DeleteOneAsync(
|
||||||
|
entity.Account, cancellationToken);
|
||||||
|
|
||||||
await _unitOfWork.SaveAsync(cancellationToken);
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
_unitOfWork.Dispose();
|
_unitOfWork.Dispose();
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ public class UpdateEmployeeCommandHandler :
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var entity = await _unitOfWork.EmployeeRepository.GetOneAsync(
|
var employee = await _unitOfWork.EmployeeRepository.GetOneAsync(
|
||||||
e =>
|
e =>
|
||||||
e.FirstName == request.FirstName &&
|
e.FirstName == request.FirstName &&
|
||||||
e.LastName == request.LastName &&
|
e.LastName == request.LastName &&
|
||||||
@ -49,30 +49,34 @@ public class UpdateEmployeeCommandHandler :
|
|||||||
e.Guid != request.Guid,
|
e.Guid != request.Guid,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
if (entity != null)
|
if (employee != null)
|
||||||
{
|
{
|
||||||
throw new DuplicateEntityException();
|
throw new DuplicateEntityException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
entity = await _unitOfWork.EmployeeRepository.GetOneAsync(
|
employee = await _unitOfWork.EmployeeRepository.GetOneAsync(
|
||||||
e => e.Guid == request.Guid, e => e.Documents, cancellationToken);
|
e => e.Guid == request.Guid, e => e.Documents, cancellationToken);
|
||||||
|
|
||||||
if (entity == null)
|
if (employee == null)
|
||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
|
a => a.Id == employee.AccountId, cancellationToken);
|
||||||
|
|
||||||
entity.Guid = request.Guid;
|
|
||||||
entity.FirstName = request.FirstName;
|
|
||||||
entity.LastName = request.LastName;
|
|
||||||
entity.Patronymic = request.Patronymic;
|
|
||||||
entity.Sex = request.Sex;
|
|
||||||
entity.BirthDate = request.BirthDate;
|
|
||||||
entity.CompanyId = parentEntity.Id;
|
|
||||||
|
|
||||||
entity.Company = parentEntity;
|
employee.Guid = request.Guid;
|
||||||
|
employee.FirstName = request.FirstName;
|
||||||
|
employee.LastName = request.LastName;
|
||||||
|
employee.Patronymic = request.Patronymic;
|
||||||
|
employee.Sex = request.Sex;
|
||||||
|
employee.BirthDate = request.BirthDate;
|
||||||
|
employee.CompanyId = parentEntity.Id;
|
||||||
|
|
||||||
|
employee.Company = parentEntity;
|
||||||
|
employee.Account = account;
|
||||||
|
|
||||||
|
|
||||||
var requestEmployeeDocuments = request.Documents.Select(
|
var requestEmployeeDocuments = request.Documents.Select(
|
||||||
@ -82,27 +86,27 @@ public class UpdateEmployeeCommandHandler :
|
|||||||
Information = d.Information
|
Information = d.Information
|
||||||
});
|
});
|
||||||
|
|
||||||
var commonEmployeeDocuments = entity.Documents.IntersectBy(
|
var commonEmployeeDocuments = employee.Documents.IntersectBy(
|
||||||
requestEmployeeDocuments.Select(
|
requestEmployeeDocuments.Select(
|
||||||
ed => (ed.DocumentType, ed.Information)),
|
ed => (ed.DocumentType, ed.Information)),
|
||||||
ed => (ed.DocumentType, ed.Information));
|
ed => (ed.DocumentType, ed.Information));
|
||||||
|
|
||||||
var newEmployeeDocuments = requestEmployeeDocuments.ExceptBy(
|
var newEmployeeDocuments = requestEmployeeDocuments.ExceptBy(
|
||||||
entity.Documents.Select(ed => (ed.DocumentType, ed.Information)),
|
employee.Documents.Select(ed => (ed.DocumentType, ed.Information)),
|
||||||
ed => (ed.DocumentType, ed.Information));
|
ed => (ed.DocumentType, ed.Information));
|
||||||
|
|
||||||
var combinedEmployeeDocuments = commonEmployeeDocuments.UnionBy(
|
var combinedEmployeeDocuments = commonEmployeeDocuments.UnionBy(
|
||||||
newEmployeeDocuments, ed => (ed.DocumentType, ed.Information));
|
newEmployeeDocuments, ed => (ed.DocumentType, ed.Information));
|
||||||
|
|
||||||
entity.Documents = combinedEmployeeDocuments.ToList();
|
employee.Documents = combinedEmployeeDocuments.ToList();
|
||||||
|
|
||||||
|
|
||||||
entity = await _unitOfWork.EmployeeRepository.UpdateOneAsync(
|
employee = await _unitOfWork.EmployeeRepository.UpdateOneAsync(
|
||||||
entity, cancellationToken);
|
employee, cancellationToken);
|
||||||
|
|
||||||
await _unitOfWork.SaveAsync(cancellationToken);
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
_unitOfWork.Dispose();
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
return _mapper.Map<EmployeeDto>(entity);
|
return _mapper.Map<EmployeeDto>(employee);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
src/Application/Employees/EmployeeAccountDto.cs
Normal file
21
src/Application/Employees/EmployeeAccountDto.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Employees;
|
||||||
|
|
||||||
|
public sealed class EmployeeAccountDto : IMapFrom<Account>
|
||||||
|
{
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
public void Mapping(MappingProfile profile)
|
||||||
|
{
|
||||||
|
profile.CreateMap<Account, EmployeeAccountDto>()
|
||||||
|
.ForMember(
|
||||||
|
d => d.Uuid,
|
||||||
|
opt => opt.MapFrom(s => s.Guid));
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,8 @@ public sealed class EmployeeDto : IMapFrom<Employee>
|
|||||||
|
|
||||||
public ICollection<EmployeeDocumentDto> Documents { get; set; }
|
public ICollection<EmployeeDocumentDto> Documents { get; set; }
|
||||||
|
|
||||||
|
public EmployeeAccountDto Account { get; set; }
|
||||||
|
|
||||||
public void Mapping(MappingProfile profile)
|
public void Mapping(MappingProfile profile)
|
||||||
{
|
{
|
||||||
profile.CreateMap<Employee, EmployeeDto>()
|
profile.CreateMap<Employee, EmployeeDto>()
|
||||||
|
@ -33,13 +33,18 @@ public class GetEmployeeQueryHandler :
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Hydrate employees with companies
|
// Hydrate employee
|
||||||
|
|
||||||
var company = await _unitOfWork.CompanyRepository.GetOneAsync(
|
var company = await _unitOfWork.CompanyRepository.GetOneAsync(
|
||||||
e => e.Id == entity.CompanyId, cancellationToken);
|
e => e.Id == entity.CompanyId, cancellationToken);
|
||||||
|
|
||||||
entity.Company = company;
|
entity.Company = company;
|
||||||
|
|
||||||
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
|
e => e.Id == entity.AccountId, cancellationToken);
|
||||||
|
|
||||||
|
entity.Account = account;
|
||||||
|
|
||||||
|
|
||||||
_unitOfWork.Dispose();
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ public class GetEmployeesPageQueryHandler :
|
|||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
// Hydrate employees with companies
|
// Hydrate employees
|
||||||
|
|
||||||
var companies = await _unitOfWork.CompanyRepository.GetPageAsync(
|
var companies = await _unitOfWork.CompanyRepository.GetPageAsync(
|
||||||
e => paginatedList.Items.Select(e => e.CompanyId).Contains(e.Id),
|
e => paginatedList.Items.Select(e => e.CompanyId).Contains(e.Id),
|
||||||
@ -61,6 +61,17 @@ public class GetEmployeesPageQueryHandler :
|
|||||||
companies.Items.First(c => c.Id == employee.CompanyId);
|
companies.Items.First(c => c.Id == employee.CompanyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var accountIds = paginatedList.Items.Select(e => e.AccountId);
|
||||||
|
var accounts = await _unitOfWork.AccountRepository.GetPageAsync(
|
||||||
|
e => accountIds.Contains(e.Id),
|
||||||
|
1, paginatedList.Items.Count, cancellationToken);
|
||||||
|
|
||||||
|
foreach (var employee in paginatedList.Items)
|
||||||
|
{
|
||||||
|
employee.Account =
|
||||||
|
accounts.Items.First(a => a.Id == employee.AccountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var mappedItems = _mapper
|
var mappedItems = _mapper
|
||||||
.ProjectTo<EmployeeDto>(paginatedList.Items.AsQueryable());
|
.ProjectTo<EmployeeDto>(paginatedList.Items.AsQueryable());
|
||||||
|
@ -16,4 +16,11 @@ public sealed class AddEmployeeViewModel
|
|||||||
public Guid CompanyUuid { get; set; }
|
public Guid CompanyUuid { get; set; }
|
||||||
|
|
||||||
public ICollection<EmployeeDocumentViewModel> Documents { get; set; }
|
public ICollection<EmployeeDocumentViewModel> Documents { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
public string Password { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
using cuqmbr.TravelGuide.Domain.Enums;
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.AddAccount;
|
namespace cuqmbr.TravelGuide.Application.Identity
|
||||||
|
.Accounts.Commands.AddAccount;
|
||||||
|
|
||||||
public record AddAccountCommand : IRequest<AccountDto>
|
public record AddAccountCommand : IRequest<AccountDto>
|
||||||
{
|
{
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
|
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
@ -3,7 +3,8 @@ using cuqmbr.TravelGuide.Application.Common.Services;
|
|||||||
using cuqmbr.TravelGuide.Domain.Enums;
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
using MediatR.Behaviors.Authorization;
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.AddAccount;
|
namespace cuqmbr.TravelGuide.Application
|
||||||
|
.Identity.Accounts.Commands.AddAccount;
|
||||||
|
|
||||||
public class AddAccountCommandAuthorizer :
|
public class AddAccountCommandAuthorizer :
|
||||||
AbstractRequestAuthorizer<AddAccountCommand>
|
AbstractRequestAuthorizer<AddAccountCommand>
|
||||||
|
@ -7,34 +7,33 @@ using cuqmbr.TravelGuide.Application.Common.Services;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.AddAccount;
|
namespace cuqmbr.TravelGuide.Application
|
||||||
|
.Identity.Accounts.Commands.AddAccount;
|
||||||
|
|
||||||
public class AddAccountCommandHandler :
|
public class AddAccountCommandHandler :
|
||||||
IRequestHandler<AddAccountCommand, AccountDto>
|
IRequestHandler<AddAccountCommand, AccountDto>
|
||||||
{
|
{
|
||||||
private readonly UnitOfWork _unitOfWork;
|
private readonly UnitOfWork _unitOfWork;
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
private readonly PasswordHasherService _passwordHasherService;
|
private readonly PasswordHasherService _passwordHasher;
|
||||||
|
|
||||||
public AddAccountCommandHandler(
|
public AddAccountCommandHandler(UnitOfWork unitOfWork,
|
||||||
UnitOfWork unitOfWork,
|
IMapper mapper, PasswordHasherService passwordHasher)
|
||||||
IMapper mapper,
|
|
||||||
PasswordHasherService passwordHasherService)
|
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
_passwordHasherService = passwordHasherService;
|
_passwordHasher = passwordHasher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<AccountDto> Handle(
|
public async Task<AccountDto> Handle(
|
||||||
AddAccountCommand request,
|
AddAccountCommand request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var user = await _unitOfWork.AccountRepository.GetOneAsync(
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
e => e.Email == request.Email,
|
e => e.Email == request.Email,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
if (user != null)
|
if (account != null)
|
||||||
{
|
{
|
||||||
throw new DuplicateEntityException();
|
throw new DuplicateEntityException();
|
||||||
}
|
}
|
||||||
@ -47,15 +46,16 @@ public class AddAccountCommandHandler :
|
|||||||
.Items;
|
.Items;
|
||||||
|
|
||||||
var salt = RandomNumberGenerator.GetBytes(128 / 8);
|
var salt = RandomNumberGenerator.GetBytes(128 / 8);
|
||||||
var hash = await _passwordHasherService.HashAsync(
|
var hash = await _passwordHasher.HashAsync(
|
||||||
Encoding.UTF8.GetBytes(request.Password),
|
Encoding.UTF8.GetBytes(request.Password),
|
||||||
salt, cancellationToken);
|
salt, cancellationToken);
|
||||||
|
|
||||||
var saltBase64 = Convert.ToBase64String(salt);
|
var saltBase64 = Convert.ToBase64String(salt);
|
||||||
var hashBase64 = Convert.ToBase64String(hash);
|
var hashBase64 = Convert.ToBase64String(hash);
|
||||||
|
|
||||||
user = new Account()
|
account = new Account()
|
||||||
{
|
{
|
||||||
|
Username = request.Username,
|
||||||
Email = request.Email,
|
Email = request.Email,
|
||||||
PasswordHash = hashBase64,
|
PasswordHash = hashBase64,
|
||||||
PasswordSalt = saltBase64,
|
PasswordSalt = saltBase64,
|
||||||
@ -66,12 +66,12 @@ public class AddAccountCommandHandler :
|
|||||||
.ToArray()
|
.ToArray()
|
||||||
};
|
};
|
||||||
|
|
||||||
user = await _unitOfWork.AccountRepository.AddOneAsync(
|
account = await _unitOfWork.AccountRepository.AddOneAsync(
|
||||||
user, cancellationToken);
|
account, cancellationToken);
|
||||||
|
|
||||||
await _unitOfWork.SaveAsync(cancellationToken);
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
_unitOfWork.Dispose();
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
return _mapper.Map<AccountDto>(user);
|
return _mapper.Map<AccountDto>(account);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,37 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.FluentValidation;
|
using cuqmbr.TravelGuide.Application.Common.FluentValidation;
|
||||||
using cuqmbr.TravelGuide.Application.Common.Services;
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.AddAccount;
|
namespace cuqmbr.TravelGuide.Application
|
||||||
|
.Identity.Accounts.Commands.AddAccount;
|
||||||
|
|
||||||
public class AddAccountCommandValidator : AbstractValidator<AddAccountCommand>
|
public class AddAccountCommandValidator :
|
||||||
|
AbstractValidator<AddAccountCommand>
|
||||||
{
|
{
|
||||||
public AddAccountCommandValidator(
|
public AddAccountCommandValidator(
|
||||||
IStringLocalizer localizer,
|
IStringLocalizer localizer,
|
||||||
SessionCultureService cultureService)
|
SessionCultureService cultureService)
|
||||||
{
|
{
|
||||||
|
RuleFor(v => v.Username)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MinimumLength(1)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MinimumLength"],
|
||||||
|
1))
|
||||||
|
.MaximumLength(32)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
32))
|
||||||
|
.IsUsername()
|
||||||
|
.WithMessage(localizer["FluentValidation.IsUsername"]);
|
||||||
|
|
||||||
RuleFor(v => v.Email)
|
RuleFor(v => v.Email)
|
||||||
.NotEmpty()
|
.NotEmpty()
|
||||||
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
@ -32,5 +53,18 @@ public class AddAccountCommandValidator : AbstractValidator<AddAccountCommand>
|
|||||||
cultureService.Culture,
|
cultureService.Culture,
|
||||||
localizer["FluentValidation.MaximumLength"],
|
localizer["FluentValidation.MaximumLength"],
|
||||||
64));
|
64));
|
||||||
|
|
||||||
|
RuleFor(v => v.Roles ?? new IdentityRole[0])
|
||||||
|
.IsUnique(r => r)
|
||||||
|
.WithMessage(localizer["FluentValidation.IsUnique"]);
|
||||||
|
|
||||||
|
RuleForEach(v => v.Roles)
|
||||||
|
.Must(r => IdentityRole.Enumerations.ContainsValue(r))
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
localizer["FluentValidation.MustBeInEnum"],
|
||||||
|
String.Join(
|
||||||
|
", ",
|
||||||
|
IdentityRole.Enumerations.Values.Select(e => e.Name))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity
|
||||||
|
.Accounts.Commands.DeleteAccount;
|
||||||
|
|
||||||
|
public record DeleteAccountCommand : IRequest
|
||||||
|
{
|
||||||
|
public Guid Guid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity
|
||||||
|
.Accounts.Commands.DeleteAccount;
|
||||||
|
|
||||||
|
public class DeleteAccountCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<DeleteAccountCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public DeleteAccountCommandAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(DeleteAccountCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles = [IdentityRole.Administrator],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.DeleteAccount;
|
||||||
|
|
||||||
|
public class DeleteAccountCommandHandler : IRequestHandler<DeleteAccountCommand>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public DeleteAccountCommandHandler(UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(
|
||||||
|
DeleteAccountCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, cancellationToken);
|
||||||
|
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _unitOfWork.AccountRepository.DeleteOneAsync(
|
||||||
|
account, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.DeleteAccount;
|
||||||
|
|
||||||
|
public class DeleteAccountCommandValidator : AbstractValidator<DeleteAccountCommand>
|
||||||
|
{
|
||||||
|
public DeleteAccountCommandValidator(IStringLocalizer localizer)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application
|
||||||
|
.Identity.Accounts.Commands.UpdateAccount;
|
||||||
|
|
||||||
|
public record UpdateAccountCommand : IRequest<AccountDto>
|
||||||
|
{
|
||||||
|
public Guid Guid { get; set; }
|
||||||
|
|
||||||
|
public string? Username { get; set; }
|
||||||
|
|
||||||
|
public string? Email { get; set; }
|
||||||
|
|
||||||
|
public string? Password { get; set; }
|
||||||
|
|
||||||
|
public ICollection<IdentityRole>? Roles { get; set; }
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.UpdateAccount;
|
||||||
|
|
||||||
|
public class UpdateAccountCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<UpdateAccountCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public UpdateAccountCommandAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(UpdateAccountCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles = [IdentityRole.Administrator],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application
|
||||||
|
.Identity.Accounts.Commands.UpdateAccount;
|
||||||
|
|
||||||
|
public class UpdateAccountCommandHandler :
|
||||||
|
IRequestHandler<UpdateAccountCommand, AccountDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
private readonly PasswordHasherService _passwordHasher;
|
||||||
|
|
||||||
|
public UpdateAccountCommandHandler(UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper, PasswordHasherService passwordHasher)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
_passwordHasher = passwordHasher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AccountDto> Handle(
|
||||||
|
UpdateAccountCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var account = await _unitOfWork.AccountRepository
|
||||||
|
.GetOneAsync(e => e.Guid == request.Guid,
|
||||||
|
e => e.AccountRoles, cancellationToken);
|
||||||
|
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
account.Username = request.Username ?? account.Username;
|
||||||
|
account.Email = request.Email ?? account.Email;
|
||||||
|
|
||||||
|
if (request.Password != null)
|
||||||
|
{
|
||||||
|
var salt = RandomNumberGenerator.GetBytes(128 / 8);
|
||||||
|
var hash = await _passwordHasher.HashAsync(
|
||||||
|
Encoding.UTF8.GetBytes(request.Password),
|
||||||
|
salt, cancellationToken);
|
||||||
|
|
||||||
|
var saltBase64 = Convert.ToBase64String(salt);
|
||||||
|
var hashBase64 = Convert.ToBase64String(hash);
|
||||||
|
|
||||||
|
account.PasswordHash = hashBase64;
|
||||||
|
account.PasswordSalt = saltBase64;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (request.Roles != null)
|
||||||
|
{
|
||||||
|
var requestRoleIds = (await _unitOfWork.RoleRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
r => request.Roles.Contains(r.Value),
|
||||||
|
1, request.Roles.Count, cancellationToken))
|
||||||
|
.Items
|
||||||
|
.Select(r => r.Id);
|
||||||
|
|
||||||
|
var accountRoles = account.AccountRoles;
|
||||||
|
var accountRoleIds = accountRoles.Select(ar => ar.RoleId);
|
||||||
|
|
||||||
|
var commonRoleIds = requestRoleIds.Intersect(accountRoleIds);
|
||||||
|
|
||||||
|
var newRoleIds = requestRoleIds.Except(accountRoleIds);
|
||||||
|
|
||||||
|
var combinedRoleIds = commonRoleIds.Union(newRoleIds);
|
||||||
|
|
||||||
|
account.AccountRoles = combinedRoleIds.Select(rId =>
|
||||||
|
new AccountRole()
|
||||||
|
{
|
||||||
|
Id = accountRoles.FirstOrDefault(ar =>
|
||||||
|
ar.RoleId == rId)?.Id ?? default,
|
||||||
|
RoleId = rId
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var accountRoleIds = account.AccountRoles.Select(ar => ar.RoleId);
|
||||||
|
var accountRoles = (await _unitOfWork.AccountRoleRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
ar => accountRoleIds.Contains(ar.RoleId),
|
||||||
|
ar => ar.Role,
|
||||||
|
1, accountRoleIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
account.AccountRoles = accountRoles.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
account = await _unitOfWork.AccountRepository.UpdateOneAsync(
|
||||||
|
account, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return _mapper.Map<AccountDto>(account);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.FluentValidation;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application
|
||||||
|
.Identity.Accounts.Commands.UpdateAccount;
|
||||||
|
|
||||||
|
public class UpdateAccountCommandValidator :
|
||||||
|
AbstractValidator<UpdateAccountCommand>
|
||||||
|
{
|
||||||
|
public UpdateAccountCommandValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
SessionCultureService cultureService)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.Username)
|
||||||
|
.MinimumLength(1)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MinimumLength"],
|
||||||
|
1))
|
||||||
|
.MaximumLength(32)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
32))
|
||||||
|
.IsUsername()
|
||||||
|
.WithMessage(localizer["FluentValidation.IsUsername"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.Email)
|
||||||
|
.IsEmail()
|
||||||
|
.WithMessage(localizer["FluentValidation.IsEmail"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.Password)
|
||||||
|
.MinimumLength(8)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MinimumLength"],
|
||||||
|
8))
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
|
|
||||||
|
RuleFor(v => v.Roles ?? new IdentityRole[0])
|
||||||
|
.IsUnique(r => r)
|
||||||
|
.WithMessage(localizer["FluentValidation.IsUnique"]);
|
||||||
|
|
||||||
|
RuleForEach(v => v.Roles)
|
||||||
|
.Must(r => IdentityRole.Enumerations.ContainsValue(r))
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
localizer["FluentValidation.MustBeInEnum"],
|
||||||
|
String.Join(
|
||||||
|
", ",
|
||||||
|
IdentityRole.Enumerations.Values.Select(e => e.Name))));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccount;
|
||||||
|
|
||||||
|
public record GetAccountQuery : IRequest<AccountDto>
|
||||||
|
{
|
||||||
|
public Guid Guid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccount;
|
||||||
|
|
||||||
|
public class GetAccountQueryAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<GetAccountQuery>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public GetAccountQueryAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(GetAccountQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles = [IdentityRole.Administrator],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using AutoMapper;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccount;
|
||||||
|
|
||||||
|
public class GetAccountQueryHandler :
|
||||||
|
IRequestHandler<GetAccountQuery, AccountDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public GetAccountQueryHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AccountDto> Handle(
|
||||||
|
GetAccountQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, e => e.AccountRoles,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var accountRoleIds = account.AccountRoles.Select(ar => ar.RoleId);
|
||||||
|
var accountRoles = (await _unitOfWork.AccountRoleRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
ar => accountRoleIds.Contains(ar.RoleId),
|
||||||
|
ar => ar.Role,
|
||||||
|
1, accountRoleIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
account.AccountRoles = accountRoles.ToList();
|
||||||
|
|
||||||
|
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return _mapper.Map<AccountDto>(account);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccount;
|
||||||
|
|
||||||
|
public class GetAccountQueryValidator : AbstractValidator<GetAccountQuery>
|
||||||
|
{
|
||||||
|
public GetAccountQueryValidator(IStringLocalizer localizer)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccountsPage;
|
||||||
|
|
||||||
|
public record GetAccountsPageQuery : IRequest<PaginatedList<AccountDto>>
|
||||||
|
{
|
||||||
|
public int PageNumber { get; set; } = 1;
|
||||||
|
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
|
||||||
|
public string Search { get; set; } = String.Empty;
|
||||||
|
|
||||||
|
public string Sort { get; set; } = String.Empty;
|
||||||
|
|
||||||
|
public ICollection<IdentityRole>? Roles { get; set; }
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccountsPage;
|
||||||
|
|
||||||
|
public class GetAccountsPageQueryAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<GetAccountsPageQuery>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public GetAccountsPageQueryAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(GetAccountsPageQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles = [IdentityRole.Administrator],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Extensions;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccountsPage;
|
||||||
|
|
||||||
|
public class GetAccountsPageQueryHandler :
|
||||||
|
IRequestHandler<GetAccountsPageQuery, PaginatedList<AccountDto>>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public GetAccountsPageQueryHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PaginatedList<AccountDto>> Handle(
|
||||||
|
GetAccountsPageQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var paginatedList = await _unitOfWork.AccountRepository.GetPageAsync(
|
||||||
|
a =>
|
||||||
|
(a.Username.ToLower().Contains(request.Search.ToLower()) ||
|
||||||
|
a.Email.ToLower().Contains(request.Search.ToLower())) &&
|
||||||
|
(request.Roles != null
|
||||||
|
? request.Roles.All(r => a.AccountRoles.Any(ar => ar.Role.Value == r))
|
||||||
|
: true),
|
||||||
|
a => a.AccountRoles,
|
||||||
|
request.PageNumber, request.PageSize, cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
|
var accounts = paginatedList.Items;
|
||||||
|
|
||||||
|
var accountsRoleIds = accounts
|
||||||
|
.SelectMany(a => a.AccountRoles)
|
||||||
|
.Select(ar => ar.RoleId)
|
||||||
|
.Distinct();
|
||||||
|
|
||||||
|
var roles = (await _unitOfWork.RoleRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
r => accountsRoleIds.Contains(r.Id),
|
||||||
|
1, accountsRoleIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
foreach (var account in accounts)
|
||||||
|
{
|
||||||
|
account.AccountRoles = account.AccountRoles.Select(ar =>
|
||||||
|
new AccountRole()
|
||||||
|
{
|
||||||
|
RoleId = ar.RoleId,
|
||||||
|
Role = roles.Single(r => r.Id == ar.RoleId),
|
||||||
|
AccountId = account.Id,
|
||||||
|
Account = account
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var mappedItems = _mapper
|
||||||
|
.ProjectTo<AccountDto>(accounts.AsQueryable());
|
||||||
|
|
||||||
|
mappedItems = QueryableExtension<AccountDto>
|
||||||
|
.ApplySort(mappedItems, request.Sort);
|
||||||
|
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return new PaginatedList<AccountDto>(
|
||||||
|
mappedItems.ToList(),
|
||||||
|
paginatedList.TotalCount, request.PageNumber,
|
||||||
|
request.PageSize);
|
||||||
|
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccountsPage;
|
||||||
|
|
||||||
|
public class GetAccountsPageQueryValidator : AbstractValidator<GetAccountsPageQuery>
|
||||||
|
{
|
||||||
|
public GetAccountsPageQueryValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
SessionCultureService cultureService)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.PageNumber)
|
||||||
|
.GreaterThanOrEqualTo(1)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||||
|
1));
|
||||||
|
|
||||||
|
RuleFor(v => v.PageSize)
|
||||||
|
.GreaterThanOrEqualTo(1)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||||
|
1))
|
||||||
|
.LessThanOrEqualTo(50)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.LessThanOrEqualTo"],
|
||||||
|
50));
|
||||||
|
|
||||||
|
RuleFor(v => v.Search)
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@ namespace cuqmbr.TravelGuide.Application.Identity.Accounts.ViewModels;
|
|||||||
|
|
||||||
public sealed class AddAccountViewModel
|
public sealed class AddAccountViewModel
|
||||||
{
|
{
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
|
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.ViewModels;
|
||||||
|
|
||||||
|
public sealed class GetAccountsPageFilterViewModel
|
||||||
|
{
|
||||||
|
public ICollection<string>? Roles { get; set; }
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Identity.Accounts.ViewModels;
|
||||||
|
|
||||||
|
public sealed class UpdateAccountViewModel
|
||||||
|
{
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public string? Username { get; set; }
|
||||||
|
|
||||||
|
public string? Email { get; set; }
|
||||||
|
|
||||||
|
public string? Password { get; set; }
|
||||||
|
|
||||||
|
public ICollection<string>? Roles { get; set; }
|
||||||
|
}
|
@ -17,6 +17,8 @@ public record GetPaymentLinkCommand : IRequest<PaymentLinkDto>
|
|||||||
|
|
||||||
public DateOnly PassangerBirthDate { get; set; }
|
public DateOnly PassangerBirthDate { get; set; }
|
||||||
|
|
||||||
|
public string? PassangerEmail { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public ICollection<TicketGroupPaymentTicketModel> Tickets { get; set; }
|
public ICollection<TicketGroupPaymentTicketModel> Tickets { get; set; }
|
||||||
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
using cuqmbr.TravelGuide.Application.Common.Services;
|
|
||||||
using cuqmbr.TravelGuide.Domain.Enums;
|
|
||||||
using MediatR.Behaviors.Authorization;
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Payments.LiqPay
|
namespace cuqmbr.TravelGuide.Application.Payments.LiqPay
|
||||||
@ -9,24 +7,8 @@ namespace cuqmbr.TravelGuide.Application.Payments.LiqPay
|
|||||||
public class GetPaymentLinkCommandAuthorizer :
|
public class GetPaymentLinkCommandAuthorizer :
|
||||||
AbstractRequestAuthorizer<GetPaymentLinkCommand>
|
AbstractRequestAuthorizer<GetPaymentLinkCommand>
|
||||||
{
|
{
|
||||||
private readonly SessionUserService _sessionUserService;
|
|
||||||
|
|
||||||
public GetPaymentLinkCommandAuthorizer(SessionUserService sessionUserService)
|
|
||||||
{
|
|
||||||
_sessionUserService = sessionUserService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void BuildPolicy(GetPaymentLinkCommand request)
|
public override void BuildPolicy(GetPaymentLinkCommand request)
|
||||||
{
|
{
|
||||||
UseRequirement(new MustBeAuthenticatedRequirement
|
UseRequirement(new AllowAllRequirement());
|
||||||
{
|
|
||||||
IsAuthenticated= _sessionUserService.IsAuthenticated
|
|
||||||
});
|
|
||||||
|
|
||||||
UseRequirement(new MustBeInRolesRequirement
|
|
||||||
{
|
|
||||||
RequiredRoles = [IdentityRole.Administrator],
|
|
||||||
UserRoles = _sessionUserService.Roles
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,16 +21,30 @@ public class GetPaymentLinkCommandHandler :
|
|||||||
|
|
||||||
private readonly IStringLocalizer _localizer;
|
private readonly IStringLocalizer _localizer;
|
||||||
|
|
||||||
|
private readonly EmailSenderService _emailSender;
|
||||||
|
|
||||||
|
private readonly SessionTimeZoneService _sessionTimeZoneService;
|
||||||
|
private readonly SessionCultureService _sessionCultureService;
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
public GetPaymentLinkCommandHandler(
|
public GetPaymentLinkCommandHandler(
|
||||||
UnitOfWork unitOfWork,
|
UnitOfWork unitOfWork,
|
||||||
CurrencyConverterService currencyConverterService,
|
CurrencyConverterService currencyConverterService,
|
||||||
LiqPayPaymentService liqPayPaymentService,
|
LiqPayPaymentService liqPayPaymentService,
|
||||||
IStringLocalizer localizer)
|
IStringLocalizer localizer,
|
||||||
|
EmailSenderService emailSender,
|
||||||
|
SessionTimeZoneService SessionTimeZoneService,
|
||||||
|
SessionCultureService sessionCultureService,
|
||||||
|
SessionUserService sessionUserService)
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_currencyConverterService = currencyConverterService;
|
_currencyConverterService = currencyConverterService;
|
||||||
_liqPayPaymentService = liqPayPaymentService;
|
_liqPayPaymentService = liqPayPaymentService;
|
||||||
_localizer = localizer;
|
_localizer = localizer;
|
||||||
|
_emailSender = emailSender;
|
||||||
|
_sessionTimeZoneService = SessionTimeZoneService;
|
||||||
|
_sessionCultureService = sessionCultureService;
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PaymentLinkDto> Handle(
|
public async Task<PaymentLinkDto> Handle(
|
||||||
@ -322,7 +336,7 @@ public class GetPaymentLinkCommandHandler :
|
|||||||
// TODO: This counts departure address stop time which is
|
// TODO: This counts departure address stop time which is
|
||||||
// not wrong but may be not desired.
|
// not wrong but may be not desired.
|
||||||
var timeToDeparture = verad
|
var timeToDeparture = verad
|
||||||
.TakeWhile(rad => rad.Id != departureRouteAddressId)
|
.TakeWhile(rad => rad.RouteAddressId != departureRouteAddressId)
|
||||||
.Aggregate(TimeSpan.Zero, (sum, next) =>
|
.Aggregate(TimeSpan.Zero, (sum, next) =>
|
||||||
sum + next.TimeToNextAddress + next.CurrentAddressStopTime);
|
sum + next.TimeToNextAddress + next.CurrentAddressStopTime);
|
||||||
|
|
||||||
@ -336,7 +350,7 @@ public class GetPaymentLinkCommandHandler :
|
|||||||
|
|
||||||
|
|
||||||
var costToDeparture = verad
|
var costToDeparture = verad
|
||||||
.TakeWhile(rad => rad.Id != departureRouteAddressId)
|
.TakeWhile(rad => rad.RouteAddressId != departureRouteAddressId)
|
||||||
.Aggregate((decimal)0, (sum, next) =>
|
.Aggregate((decimal)0, (sum, next) =>
|
||||||
sum + next.CostToNextAddress);
|
sum + next.CostToNextAddress);
|
||||||
|
|
||||||
@ -398,6 +412,10 @@ public class GetPaymentLinkCommandHandler :
|
|||||||
.Items;
|
.Items;
|
||||||
|
|
||||||
|
|
||||||
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
|
e => e.Guid == _sessionUserService.Guid, cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
var travelTime =
|
var travelTime =
|
||||||
ticketsDetails.OrderBy(td => td.order).Last().arrivalTime -
|
ticketsDetails.OrderBy(td => td.order).Last().arrivalTime -
|
||||||
ticketsDetails.OrderBy(td => td.order).First().departureTime;
|
ticketsDetails.OrderBy(td => td.order).First().departureTime;
|
||||||
@ -412,6 +430,7 @@ public class GetPaymentLinkCommandHandler :
|
|||||||
PurchaseTime = DateTimeOffset.UtcNow,
|
PurchaseTime = DateTimeOffset.UtcNow,
|
||||||
Status = TicketStatus.Reserved,
|
Status = TicketStatus.Reserved,
|
||||||
TravelTime = travelTime,
|
TravelTime = travelTime,
|
||||||
|
PassangerEmail = request.PassangerEmail,
|
||||||
Tickets = request.Tickets.Select(
|
Tickets = request.Tickets.Select(
|
||||||
t =>
|
t =>
|
||||||
{
|
{
|
||||||
@ -428,12 +447,6 @@ public class GetPaymentLinkCommandHandler :
|
|||||||
var detail = ticketsDetails
|
var detail = ticketsDetails
|
||||||
.SingleOrDefault(td => td.order == t.Order);
|
.SingleOrDefault(td => td.order == t.Order);
|
||||||
|
|
||||||
var currency = Currency.UAH;
|
|
||||||
var cost = _currencyConverterService
|
|
||||||
.ConvertAsync(
|
|
||||||
detail.cost, detail.currency, currency,
|
|
||||||
cancellationToken).Result;
|
|
||||||
|
|
||||||
return new Ticket()
|
return new Ticket()
|
||||||
{
|
{
|
||||||
DepartureRouteAddressId = departureRouteAddress.Id,
|
DepartureRouteAddressId = departureRouteAddress.Id,
|
||||||
@ -441,12 +454,12 @@ public class GetPaymentLinkCommandHandler :
|
|||||||
ArrivalRouteAddressId = arrivalRouteAddress.Id,
|
ArrivalRouteAddressId = arrivalRouteAddress.Id,
|
||||||
ArrivalRouteAddress = arrivalRouteAddress,
|
ArrivalRouteAddress = arrivalRouteAddress,
|
||||||
Order = t.Order,
|
Order = t.Order,
|
||||||
Cost = cost,
|
Cost = detail.cost,
|
||||||
Currency = currency,
|
Currency = detail.currency,
|
||||||
VehicleEnrollmentId = ve.Id
|
VehicleEnrollmentId = ve.Id
|
||||||
};
|
};
|
||||||
})
|
}).ToArray(),
|
||||||
.ToArray()
|
AccountId = account?.Id
|
||||||
};
|
};
|
||||||
|
|
||||||
entity = await _unitOfWork.TicketGroupRepository.AddOneAsync(
|
entity = await _unitOfWork.TicketGroupRepository.AddOneAsync(
|
||||||
@ -456,7 +469,11 @@ public class GetPaymentLinkCommandHandler :
|
|||||||
_unitOfWork.Dispose();
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
|
||||||
var amount = entity.Tickets.Sum(e => e.Cost);
|
var amount = entity.Tickets.Sum(e =>
|
||||||
|
_currencyConverterService
|
||||||
|
.ConvertAsync(
|
||||||
|
e.Cost, e.Currency, Currency.UAH,
|
||||||
|
cancellationToken).Result);
|
||||||
var guid = entity.Guid;
|
var guid = entity.Guid;
|
||||||
var validity = TimeSpan.FromMinutes(10);
|
var validity = TimeSpan.FromMinutes(10);
|
||||||
var resultPath = request.ResultPath;
|
var resultPath = request.ResultPath;
|
||||||
@ -465,9 +482,31 @@ public class GetPaymentLinkCommandHandler :
|
|||||||
var paymentLink = _liqPayPaymentService
|
var paymentLink = _liqPayPaymentService
|
||||||
.GetPaymentLink(
|
.GetPaymentLink(
|
||||||
amount, Currency.UAH, guid.ToString(), validity,
|
amount, Currency.UAH, guid.ToString(), validity,
|
||||||
_localizer["PaymentProcessing.TicketPaymentDescription"],
|
_localizer["PaymentProcessing.Ticket.PaymentDescription"],
|
||||||
resultPath, callbackPath);
|
resultPath, callbackPath);
|
||||||
|
|
||||||
|
if (request.PassangerEmail != null)
|
||||||
|
{
|
||||||
|
var validUntil = DateTimeOffset.UtcNow
|
||||||
|
.Add(validity)
|
||||||
|
.ToOffset(_sessionTimeZoneService.TimeZone.BaseUtcOffset);
|
||||||
|
|
||||||
|
var subject =
|
||||||
|
_localizer["PaymentProcessing.Ticket" +
|
||||||
|
".Email.PaymentCreated.Subject"];
|
||||||
|
|
||||||
|
var body = String.Format(
|
||||||
|
_sessionCultureService.Culture,
|
||||||
|
_localizer["PaymentProcessing.Ticket" +
|
||||||
|
".Email.PaymentCreated.Body"],
|
||||||
|
Currency.UAH.Round(amount), Currency.UAH.Name,
|
||||||
|
validUntil, paymentLink);
|
||||||
|
|
||||||
|
await _emailSender.SendAsync(
|
||||||
|
new[] { request.PassangerEmail }, subject,
|
||||||
|
body, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
return new PaymentLinkDto() { PaymentLink = paymentLink };
|
return new PaymentLinkDto() { PaymentLink = paymentLink };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,15 @@ public class GetPaymentLinkCommandValidator :
|
|||||||
cultureService.Culture,
|
cultureService.Culture,
|
||||||
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||||
DateOnly.FromDateTime(DateTime.UtcNow.AddYears(-100))));
|
DateOnly.FromDateTime(DateTime.UtcNow.AddYears(-100))));
|
||||||
|
|
||||||
|
When(tg => tg.PassangerEmail != null, () =>
|
||||||
|
{
|
||||||
|
RuleFor(v => v.PassangerEmail)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.IsEmail()
|
||||||
|
.WithMessage(localizer["FluentValidation.IsEmail"]);
|
||||||
|
});
|
||||||
|
|
||||||
RuleFor(tg => tg.Tickets)
|
RuleFor(tg => tg.Tickets)
|
||||||
.IsUnique(t => t.VehicleEnrollmentGuid)
|
.IsUnique(t => t.VehicleEnrollmentGuid)
|
||||||
|
@ -5,6 +5,8 @@ using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using cuqmbr.TravelGuide.Domain.Enums;
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Payments.LiqPay
|
namespace cuqmbr.TravelGuide.Application.Payments.LiqPay
|
||||||
.TicketGroups.Commands.ProcessCallback;
|
.TicketGroups.Commands.ProcessCallback;
|
||||||
@ -13,20 +15,31 @@ public class ProcessCallbackCommandHandler :
|
|||||||
IRequestHandler<ProcessCallbackCommand>
|
IRequestHandler<ProcessCallbackCommand>
|
||||||
{
|
{
|
||||||
private readonly UnitOfWork _unitOfWork;
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
private readonly LiqPayPaymentService _liqPayPaymentService;
|
private readonly LiqPayPaymentService _liqPayPaymentService;
|
||||||
|
|
||||||
|
private readonly IStringLocalizer _localizer;
|
||||||
|
|
||||||
|
private readonly EmailSenderService _emailSender;
|
||||||
|
|
||||||
public ProcessCallbackCommandHandler(
|
public ProcessCallbackCommandHandler(
|
||||||
UnitOfWork unitOfWork,
|
UnitOfWork unitOfWork,
|
||||||
LiqPayPaymentService liqPayPaymentService)
|
LiqPayPaymentService liqPayPaymentService,
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
EmailSenderService emailSender)
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_liqPayPaymentService = liqPayPaymentService;
|
_liqPayPaymentService = liqPayPaymentService;
|
||||||
|
_localizer = localizer;
|
||||||
|
_emailSender = emailSender;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(
|
public async Task Handle(
|
||||||
ProcessCallbackCommand request,
|
ProcessCallbackCommand request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
// Validate signature.
|
||||||
|
|
||||||
var isSignatureValid = _liqPayPaymentService
|
var isSignatureValid = _liqPayPaymentService
|
||||||
.IsValidSignature(request.Data, request.Signature);
|
.IsValidSignature(request.Data, request.Signature);
|
||||||
|
|
||||||
@ -35,6 +48,9 @@ public class ProcessCallbackCommandHandler :
|
|||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Parse request data.
|
||||||
|
|
||||||
var dataBytes = Convert.FromBase64String(request.Data);
|
var dataBytes = Convert.FromBase64String(request.Data);
|
||||||
var dataJson = Encoding.UTF8.GetString(dataBytes);
|
var dataJson = Encoding.UTF8.GetString(dataBytes);
|
||||||
|
|
||||||
@ -42,9 +58,11 @@ public class ProcessCallbackCommandHandler :
|
|||||||
|
|
||||||
string status = data.status;
|
string status = data.status;
|
||||||
|
|
||||||
|
|
||||||
var ticketGroupGuid = Guid.Parse((string)data.order_id);
|
var ticketGroupGuid = Guid.Parse((string)data.order_id);
|
||||||
var ticketGroup = await _unitOfWork.TicketGroupRepository
|
var ticketGroup = await _unitOfWork.TicketGroupRepository
|
||||||
.GetOneAsync(e => e.Guid == ticketGroupGuid, cancellationToken);
|
.GetOneAsync(e => e.Guid == ticketGroupGuid,
|
||||||
|
e => e.Tickets, cancellationToken);
|
||||||
|
|
||||||
if (ticketGroup == null ||
|
if (ticketGroup == null ||
|
||||||
ticketGroup.Status == TicketStatus.Purchased)
|
ticketGroup.Status == TicketStatus.Purchased)
|
||||||
@ -52,6 +70,9 @@ public class ProcessCallbackCommandHandler :
|
|||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Process callback status
|
||||||
|
|
||||||
if (status.Equals("error") || status.Equals("failure"))
|
if (status.Equals("error") || status.Equals("failure"))
|
||||||
{
|
{
|
||||||
await _unitOfWork.TicketGroupRepository
|
await _unitOfWork.TicketGroupRepository
|
||||||
@ -59,12 +80,228 @@ public class ProcessCallbackCommandHandler :
|
|||||||
}
|
}
|
||||||
else if (status.Equals("success"))
|
else if (status.Equals("success"))
|
||||||
{
|
{
|
||||||
|
// Update ticket status
|
||||||
|
|
||||||
ticketGroup.Status = TicketStatus.Purchased;
|
ticketGroup.Status = TicketStatus.Purchased;
|
||||||
await _unitOfWork.TicketGroupRepository
|
await _unitOfWork.TicketGroupRepository
|
||||||
.UpdateOneAsync(ticketGroup, cancellationToken);
|
.UpdateOneAsync(ticketGroup, cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
|
// Hydrate ticket group
|
||||||
|
|
||||||
|
var vehicleEnrollmentIds =
|
||||||
|
ticketGroup.Tickets.Select(t => t.VehicleEnrollmentId);
|
||||||
|
var vehicleEnrollments = (await _unitOfWork.VehicleEnrollmentRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
ve => vehicleEnrollmentIds.Contains(ve.Id),
|
||||||
|
ve => ve.Route.RouteAddresses,
|
||||||
|
1, vehicleEnrollmentIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
var routeAddressIds = vehicleEnrollments
|
||||||
|
.SelectMany(ve => ve.Route.RouteAddresses)
|
||||||
|
.Select(ra => ra.Id);
|
||||||
|
var routeAddressDetails = (await _unitOfWork.RouteAddressDetailRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
rad => routeAddressIds.Contains(rad.RouteAddressId),
|
||||||
|
1, routeAddressIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
var addressIds = vehicleEnrollments
|
||||||
|
.SelectMany(ve => ve.Route.RouteAddresses)
|
||||||
|
.Select(ra => ra.AddressId);
|
||||||
|
var addresses = (await _unitOfWork.AddressRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
a => addressIds.Contains(a.Id),
|
||||||
|
a => a.City.Region.Country,
|
||||||
|
1, addressIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
var vehicleIds = vehicleEnrollments
|
||||||
|
.Select(ve => ve.VehicleId);
|
||||||
|
var vehicles = (await _unitOfWork.VehicleRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
v => vehicleIds.Contains(v.Id),
|
||||||
|
v => v.Company,
|
||||||
|
1, vehicleIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
foreach (var ve in vehicleEnrollments)
|
||||||
|
{
|
||||||
|
ve.Vehicle = vehicles.Single(v => v.Id == ve.VehicleId);
|
||||||
|
|
||||||
|
foreach (var ra in ve.Route.RouteAddresses)
|
||||||
|
{
|
||||||
|
ra.Address = addresses.Single(a => a.Id == ra.AddressId);
|
||||||
|
ra.Details = routeAddressDetails
|
||||||
|
.Where(rad => rad.RouteAddressId == ra.Id)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var t in ticketGroup.Tickets)
|
||||||
|
{
|
||||||
|
t.VehicleEnrollment = vehicleEnrollments
|
||||||
|
.Single(ve => ve.Id == t.VehicleEnrollmentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Send email
|
||||||
|
|
||||||
|
if (ticketGroup.PassangerEmail != null)
|
||||||
|
{
|
||||||
|
var subject =
|
||||||
|
_localizer["PaymentProcessing.Ticket" +
|
||||||
|
".Email.PaymentCompleted.Subject"];
|
||||||
|
|
||||||
|
var ticketDetails = GetTicketDetails(ticketGroup);
|
||||||
|
|
||||||
|
var body = String.Format(
|
||||||
|
_localizer["PaymentProcessing.Ticket" +
|
||||||
|
".Email.PaymentCompleted.Body"],
|
||||||
|
ticketDetails);
|
||||||
|
|
||||||
|
await _emailSender.SendAsync(
|
||||||
|
new[] { ticketGroup.PassangerEmail }, subject,
|
||||||
|
body, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
await _unitOfWork.SaveAsync(cancellationToken);
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
_unitOfWork.Dispose();
|
_unitOfWork.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetTicketDetails(TicketGroup ticketGroup)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.AppendLine("General:");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"Ticket uuid: {ticketGroup.Guid}");
|
||||||
|
sb.AppendLine($"Purchase Time: {ticketGroup.PurchaseTime}");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
var departureRouteAddressId =
|
||||||
|
ticketGroup.Tickets.First().DepartureRouteAddressId;
|
||||||
|
var arrivalRouteAddressId =
|
||||||
|
ticketGroup.Tickets.Last().ArrivalRouteAddressId;
|
||||||
|
|
||||||
|
var departureTime =
|
||||||
|
ticketGroup.Tickets.First()
|
||||||
|
.VehicleEnrollment.GetDepartureTime(departureRouteAddressId);
|
||||||
|
var arrivalTime =
|
||||||
|
ticketGroup.Tickets.Last()
|
||||||
|
.VehicleEnrollment.GetArrivalTime(arrivalRouteAddressId);
|
||||||
|
|
||||||
|
var departureAddress =
|
||||||
|
ticketGroup.Tickets.First()
|
||||||
|
.VehicleEnrollment.Route.RouteAddresses
|
||||||
|
.Single(ra => ra.Id == departureRouteAddressId)
|
||||||
|
.Address;
|
||||||
|
var arrivalAddress =
|
||||||
|
ticketGroup.Tickets.Last()
|
||||||
|
.VehicleEnrollment.Route.RouteAddresses
|
||||||
|
.Single(ra => ra.Id == arrivalRouteAddressId)
|
||||||
|
.Address;
|
||||||
|
|
||||||
|
var departureAddressName =
|
||||||
|
$"{departureAddress.City.Region.Country.Name}, " +
|
||||||
|
$"{departureAddress.City.Region.Name}, " +
|
||||||
|
$"{departureAddress.City.Name}, " +
|
||||||
|
$"{departureAddress.Name}";
|
||||||
|
var arrivalAddressName =
|
||||||
|
$"{arrivalAddress.City.Region.Country.Name}, " +
|
||||||
|
$"{arrivalAddress.City.Region.Name}, " +
|
||||||
|
$"{arrivalAddress.City.Name}, " +
|
||||||
|
$"{arrivalAddress.Name}";
|
||||||
|
|
||||||
|
sb.AppendLine($"Departure: {departureAddressName} at {departureTime}.");
|
||||||
|
sb.AppendLine($"Arrival: {arrivalAddressName} at {arrivalTime}.");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine($"Passanger details:");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"First Name: {ticketGroup.PassangerFirstName}.");
|
||||||
|
sb.AppendLine($"Last Name: {ticketGroup.PassangerLastName}.");
|
||||||
|
sb.AppendLine($"Patronymic: {ticketGroup.PassangerPatronymic}.");
|
||||||
|
sb.AppendLine($"Sex: {ticketGroup.PassangerSex}.");
|
||||||
|
sb.AppendLine($"Birth Date: {ticketGroup.PassangerBirthDate}.");
|
||||||
|
sb.AppendLine($"Email: {ticketGroup.PassangerEmail}.");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
|
||||||
|
sb.AppendLine("Vehicle enrollments' details:");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
foreach (var t in ticketGroup.Tickets)
|
||||||
|
{
|
||||||
|
departureRouteAddressId = t.DepartureRouteAddressId;
|
||||||
|
arrivalRouteAddressId = t.ArrivalRouteAddressId;
|
||||||
|
|
||||||
|
departureTime =
|
||||||
|
t.VehicleEnrollment.GetDepartureTime(departureRouteAddressId);
|
||||||
|
arrivalTime =
|
||||||
|
t.VehicleEnrollment.GetArrivalTime(arrivalRouteAddressId);
|
||||||
|
|
||||||
|
departureAddress =
|
||||||
|
t.VehicleEnrollment.Route.RouteAddresses
|
||||||
|
.Single(ra => ra.Id == departureRouteAddressId)
|
||||||
|
.Address;
|
||||||
|
arrivalAddress =
|
||||||
|
t.VehicleEnrollment.Route.RouteAddresses
|
||||||
|
.Single(ra => ra.Id == arrivalRouteAddressId)
|
||||||
|
.Address;
|
||||||
|
|
||||||
|
departureAddressName =
|
||||||
|
$"{departureAddress.City.Region.Country.Name}, " +
|
||||||
|
$"{departureAddress.City.Region.Name}, " +
|
||||||
|
$"{departureAddress.City.Name}, " +
|
||||||
|
$"{departureAddress.Name}";
|
||||||
|
arrivalAddressName =
|
||||||
|
$"{arrivalAddress.City.Region.Country.Name}, " +
|
||||||
|
$"{arrivalAddress.City.Region.Name}, " +
|
||||||
|
$"{arrivalAddress.City.Name}, " +
|
||||||
|
$"{arrivalAddress.Name}";
|
||||||
|
|
||||||
|
var vehicle = t.VehicleEnrollment.Vehicle;
|
||||||
|
var company = vehicle.Company;
|
||||||
|
|
||||||
|
sb.AppendLine($"Departure: {departureAddressName} at {departureTime}.");
|
||||||
|
sb.AppendLine($"Arrival: {arrivalAddressName} at {arrivalTime}.");
|
||||||
|
|
||||||
|
if (vehicle is Bus)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"Vehicle: Bus, {((Bus)vehicle).Model}, " +
|
||||||
|
$"{((Bus)vehicle).Number}.");
|
||||||
|
}
|
||||||
|
else if (vehicle is Aircraft)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"Vehicle: Aircraft, {((Aircraft)vehicle).Model}, " +
|
||||||
|
$"{((Aircraft)vehicle).Number}.");
|
||||||
|
}
|
||||||
|
else if (vehicle is Train)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"Vehicle: Train, {((Train)vehicle).Model}, " +
|
||||||
|
$"{((Train)vehicle).Number}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine($"Company: {company.Name}, ({company.ContactEmail}, " +
|
||||||
|
$"{company.ContactPhoneNumber}).");
|
||||||
|
|
||||||
|
var cost = t.Currency.Round(
|
||||||
|
t.VehicleEnrollment
|
||||||
|
.GetCost(departureRouteAddressId,arrivalRouteAddressId));
|
||||||
|
sb.AppendLine($"Cost: {cost} {t.Currency.Name}");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ public sealed class TicketGroupPaymentViewModel
|
|||||||
|
|
||||||
public DateOnly PassangerBirthDate { get; set; }
|
public DateOnly PassangerBirthDate { get; set; }
|
||||||
|
|
||||||
|
public string? PassangerEmail { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public ICollection<TicketPaymentViewModel> Tickets { get; set; }
|
public ICollection<TicketPaymentViewModel> Tickets { get; set; }
|
||||||
|
|
||||||
|
@ -62,6 +62,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"PaymentProcessing": {
|
"PaymentProcessing": {
|
||||||
"TicketPaymentDescription": "Ticket purchase."
|
"Ticket": {
|
||||||
|
"PaymentDescription": "Ticket purchase.",
|
||||||
|
"Email": {
|
||||||
|
"PaymentCreated": {
|
||||||
|
"Subject": "Ticket purchase payment link.",
|
||||||
|
"Body": "You have reserved a ticket. Payment amount is {0} {1} Payment link is valid until {2}.\n\nLink: {3}"
|
||||||
|
},
|
||||||
|
"PaymentCompleted": {
|
||||||
|
"Subject": "Ticket purchase complete.",
|
||||||
|
"Body": "Payment is succeeded.\n\n\nTicket details:\n\n{0}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,46 +1,79 @@
|
|||||||
{
|
{
|
||||||
"FluentValidation": {
|
"FluentValidation": {
|
||||||
"MaximumLength": "Повинно бути менше ніж {0:G} символів.",
|
|
||||||
"NotEmpty": "Не повинно бути порожнім.",
|
"NotEmpty": "Не повинно бути порожнім.",
|
||||||
"GreaterThanOrEqualTo": "Повинно бути більше або дорівнювати {0:G}.",
|
"GreaterThanOrEqualTo": "Повинно бути більше або дорівнювати {0}.",
|
||||||
"LessThanOrEqualTo": "Повинно бути менше або дорівнювати {0:G}."
|
"LessThanOrEqualTo": "Повинно бути менше або дорівнювати {0}.",
|
||||||
|
"MinimumLength": "Довжина повинна бути більшою або дорівнювати {0} символам.",
|
||||||
|
"MaximumLength": "Довжина повинна бути меншою або дорівнювати {0} символам.",
|
||||||
|
"MustBeInEnum": "Повинно бути одним із наступних: {0}.",
|
||||||
|
"IsUsername": "Може містити латинські літери у нижньому регістрі (a-z), цифри (0-9), дефіси (-), підкреслення (_) та крапки (.).",
|
||||||
|
"IsEmail": "Повинно бути дійсною електронною адресою відповідно до RFC 5321.",
|
||||||
|
"IsPhoneNumber": "Повинно бути дійсним номером телефону відповідно до ITU-T E.164 без роздільних символів.",
|
||||||
|
"IsUnique": "Елементи колекції повинні бути унікальними."
|
||||||
|
},
|
||||||
|
"Validation": {
|
||||||
|
"DistinctOrder": "Повинно мати унікальні значення порядку.",
|
||||||
|
"SameVehicleType": "Повинно мати однаковий тип транспортного засобу.",
|
||||||
|
"DateTimeOffset": {
|
||||||
|
"GreaterThanOrEqualTo": "Повинно бути більше або дорівнювати {0:U}"
|
||||||
|
},
|
||||||
|
"VehicleEnrollments": {
|
||||||
|
"OverlapWithOther": "Наданий запис транспортного засобу перетинається за розкладом з іншим.",
|
||||||
|
"NegativeTime": "Вказаний час повинен бути додатнім проміжком часу.",
|
||||||
|
"NegativeCost": "Вказана вартість повинна бути додатнім значенням."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ExceptionHandling": {
|
"ExceptionHandling": {
|
||||||
"ValidationException": {
|
"ValidationException": {
|
||||||
"Title": "Виникла одна або декілька помилок валідації.",
|
"Title": "Виникла одна або кілька помилок валідації.",
|
||||||
"Detail": "Надані дані не задовольняють вимогам валідації."
|
"Detail": "Надані дані не відповідають вимогам валідації."
|
||||||
},
|
},
|
||||||
"RegistrationException": {
|
"RegistrationException": {
|
||||||
"Title": "Реєстрація не вдалася.",
|
"Title": "Реєстрація не вдалася.",
|
||||||
"Detail": "Електронна пошта вже зареєстрована."
|
"Detail": "Електронна пошта вже зареєстрована."
|
||||||
},
|
},
|
||||||
"UnAuthorizedException": {
|
"UnAuthorizedException": {
|
||||||
"Title": "Доступ без автентифікації заблоковано.",
|
"Title": "Неавтентифікований доступ заблоковано.",
|
||||||
"Detail": "Запит не містить дійсних автентифікаційних даних для цільового ресурсу."
|
"Detail": "Запит не містить дійсних автентифікаційних даних для цільового ресурсу."
|
||||||
},
|
},
|
||||||
"AithenticationException": {
|
"AuthenticationException": {
|
||||||
"Title": "Автентифікація не вдалася.",
|
"Title": "Автентифікація не вдалася.",
|
||||||
"Detail": "Перевірте правильність наданих облікових даних."
|
"Detail": "Перевірте правильність наданих облікових даних."
|
||||||
},
|
},
|
||||||
"LoginException": {
|
"LoginException": {
|
||||||
"Title": "Вхід не вдалий.",
|
"Title": "Вхід не вдалий.",
|
||||||
"Detail": "Надані електронна пошта та/або пароль недійсні."
|
"Detail": "Надана електронна пошта та/або пароль недійсні."
|
||||||
},
|
},
|
||||||
"ForbiddenException": {
|
"ForbiddenException": {
|
||||||
"Title": "Доступ заборонено.",
|
"Title": "Неавторизований доступ заблоковано.",
|
||||||
"Detail": "У вас недостатньо прав для виконання запиту."
|
"Detail": "У вас недостатньо прав для виконання запиту."
|
||||||
},
|
},
|
||||||
"DuplicateEntityException": {
|
"DuplicateEntityException": {
|
||||||
"Title": "Об’єкт вже існує.",
|
"Title": "Об'єкт вже існує.",
|
||||||
"Detail": "Дублювання не дозволяється."
|
"Detail": "Дублікати заборонені."
|
||||||
},
|
},
|
||||||
"NotFoundException": {
|
"NotFoundException": {
|
||||||
"Title": "Один або декілька ресурсів не знайдено.",
|
"Title": "Один або кілька ресурсів не знайдено.",
|
||||||
"Detail": "Перевірте правильність вхідних даних."
|
"Detail": "Перевірте правильність вхідних даних."
|
||||||
},
|
},
|
||||||
"UnhandledException": {
|
"UnhandledException": {
|
||||||
"Title": "Виникла одна або декілька внутрішніх помилок сервера.",
|
"Title": "Виникла одна або кілька внутрішніх помилок сервера.",
|
||||||
"Detail": "Повідомте про цю помилку службі підтримки сервісу."
|
"Detail": "Повідомте про цю помилку команді підтримки сервісу."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PaymentProcessing": {
|
||||||
|
"Ticket": {
|
||||||
|
"PaymentDescription": "Придбання квитка.",
|
||||||
|
"Email": {
|
||||||
|
"PaymentCreated": {
|
||||||
|
"Subject": "Посилання для оплати придбання квитка.",
|
||||||
|
"Body": "Ви забронювали квиток. Сума оплати становить {0} {1}. Посилання дійсне до {2}.\n\nПосилання: {3}"
|
||||||
|
},
|
||||||
|
"PaymentCompleted": {
|
||||||
|
"Subject": "Придбання квитка завершено.",
|
||||||
|
"Body": "Оплата пройшла успішно.\n\n\nОсь деталі вашого квитка:\n\n{0}"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ public class AddTicketGroupCommandHandler :
|
|||||||
var vehicleEnrollments = (await _unitOfWork.VehicleEnrollmentRepository
|
var vehicleEnrollments = (await _unitOfWork.VehicleEnrollmentRepository
|
||||||
.GetPageAsync(
|
.GetPageAsync(
|
||||||
e => vehicleEnrollmentGuids.Contains(e.Guid),
|
e => vehicleEnrollmentGuids.Contains(e.Guid),
|
||||||
e => e.Vehicle,
|
e => e.Vehicle.Company,
|
||||||
1, vehicleEnrollmentGuids.Count(), cancellationToken))
|
1, vehicleEnrollmentGuids.Count(), cancellationToken))
|
||||||
.Items;
|
.Items;
|
||||||
|
|
||||||
@ -325,7 +325,7 @@ public class AddTicketGroupCommandHandler :
|
|||||||
// TODO: This counts departure address stop time which is
|
// TODO: This counts departure address stop time which is
|
||||||
// not wrong but may be not desired.
|
// not wrong but may be not desired.
|
||||||
var timeToDeparture = verad
|
var timeToDeparture = verad
|
||||||
.TakeWhile(rad => rad.Id != departureRouteAddressId)
|
.TakeWhile(rad => rad.RouteAddressId != departureRouteAddressId)
|
||||||
.Aggregate(TimeSpan.Zero, (sum, next) =>
|
.Aggregate(TimeSpan.Zero, (sum, next) =>
|
||||||
sum + next.TimeToNextAddress + next.CurrentAddressStopTime);
|
sum + next.TimeToNextAddress + next.CurrentAddressStopTime);
|
||||||
|
|
||||||
@ -339,7 +339,7 @@ public class AddTicketGroupCommandHandler :
|
|||||||
|
|
||||||
|
|
||||||
var costToDeparture = verad
|
var costToDeparture = verad
|
||||||
.TakeWhile(rad => rad.Id != departureRouteAddressId)
|
.TakeWhile(rad => rad.RouteAddressId != departureRouteAddressId)
|
||||||
.Aggregate((decimal)0, (sum, next) =>
|
.Aggregate((decimal)0, (sum, next) =>
|
||||||
sum + next.CostToNextAddress);
|
sum + next.CostToNextAddress);
|
||||||
|
|
||||||
@ -416,7 +416,6 @@ public class AddTicketGroupCommandHandler :
|
|||||||
1, vehicleEnrollmentGuids.Count(), cancellationToken))
|
1, vehicleEnrollmentGuids.Count(), cancellationToken))
|
||||||
.Items;
|
.Items;
|
||||||
|
|
||||||
|
|
||||||
var routeAddressGuids =
|
var routeAddressGuids =
|
||||||
request.Tickets.Select(t => t.DepartureRouteAddressGuid).Concat(
|
request.Tickets.Select(t => t.DepartureRouteAddressGuid).Concat(
|
||||||
request.Tickets.Select(t => t.ArrivalRouteAddressGuid));
|
request.Tickets.Select(t => t.ArrivalRouteAddressGuid));
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups.Queries.GetTicketGroup;
|
||||||
|
|
||||||
|
public record GetTicketGroupQuery : IRequest<TicketGroupDto>
|
||||||
|
{
|
||||||
|
public Guid Guid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups.Queries.GetTicketGroup;
|
||||||
|
|
||||||
|
public class GetTicketGroupQueryAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<GetTicketGroupQuery>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public GetTicketGroupQueryAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(GetTicketGroupQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles = [IdentityRole.Administrator],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,183 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups.Queries.GetTicketGroup;
|
||||||
|
|
||||||
|
public class GetTicketGroupQueryHandler :
|
||||||
|
IRequestHandler<GetTicketGroupQuery, TicketGroupDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
private readonly CurrencyConverterService _currencyConverter;
|
||||||
|
|
||||||
|
private readonly SessionCurrencyService _sessionCurrencyService;
|
||||||
|
private readonly SessionTimeZoneService _sessionTimeZoneService;
|
||||||
|
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
public GetTicketGroupQueryHandler(UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper, CurrencyConverterService currencyConverterService,
|
||||||
|
SessionCurrencyService sessionCurrencyService,
|
||||||
|
SessionTimeZoneService sessionTimeZoneService)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
_currencyConverter = currencyConverterService;
|
||||||
|
_sessionCurrencyService = sessionCurrencyService;
|
||||||
|
_sessionTimeZoneService = sessionTimeZoneService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TicketGroupDto> Handle(
|
||||||
|
GetTicketGroupQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var ticketGroup = await _unitOfWork.TicketGroupRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, e => e.Tickets,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (ticketGroup == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Hydrate
|
||||||
|
|
||||||
|
var vehicleEnrollmentIds =
|
||||||
|
ticketGroup.Tickets.Select(t => t.VehicleEnrollmentId);
|
||||||
|
var vehicleEnrollments = (await _unitOfWork.VehicleEnrollmentRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
ve => vehicleEnrollmentIds.Contains(ve.Id),
|
||||||
|
ve => ve.Route.RouteAddresses,
|
||||||
|
1, vehicleEnrollmentIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
var routeAddressIds = vehicleEnrollments
|
||||||
|
.SelectMany(ve => ve.Route.RouteAddresses)
|
||||||
|
.Select(ra => ra.Id);
|
||||||
|
var routeAddressDetails = (await _unitOfWork.RouteAddressDetailRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
rad => routeAddressIds.Contains(rad.RouteAddressId),
|
||||||
|
1, routeAddressIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
var addressIds = vehicleEnrollments
|
||||||
|
.SelectMany(ve => ve.Route.RouteAddresses)
|
||||||
|
.Select(ra => ra.AddressId);
|
||||||
|
var addresses = (await _unitOfWork.AddressRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
a => addressIds.Contains(a.Id),
|
||||||
|
a => a.City.Region.Country,
|
||||||
|
1, addressIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
var vehicleIds = vehicleEnrollments
|
||||||
|
.Select(ve => ve.VehicleId);
|
||||||
|
var vehicles = (await _unitOfWork.VehicleRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
v => vehicleIds.Contains(v.Id),
|
||||||
|
v => v.Company,
|
||||||
|
1, vehicleIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
|
a => a.Id == ticketGroup.AccountId, cancellationToken);
|
||||||
|
|
||||||
|
if (ticketGroup.AccountId != null)
|
||||||
|
{
|
||||||
|
ticketGroup.Account = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var ve in vehicleEnrollments)
|
||||||
|
{
|
||||||
|
ve.Vehicle = vehicles.Single(v => v.Id == ve.VehicleId);
|
||||||
|
|
||||||
|
foreach (var ra in ve.Route.RouteAddresses)
|
||||||
|
{
|
||||||
|
ra.Address = addresses.Single(a => a.Id == ra.AddressId);
|
||||||
|
ra.Details = routeAddressDetails
|
||||||
|
.Where(rad => rad.RouteAddressId == ra.Id)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Replace with AutoMapper resolvers
|
||||||
|
// Convert currency and apply session time zone
|
||||||
|
|
||||||
|
var convertTasks = new List<Task>();
|
||||||
|
|
||||||
|
foreach (var t in ticketGroup.Tickets)
|
||||||
|
{
|
||||||
|
convertTasks.Add(Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
t.VehicleEnrollment.DepartureTime =
|
||||||
|
TimeZoneInfo.ConvertTime(t.VehicleEnrollment.DepartureTime,
|
||||||
|
_sessionTimeZoneService.TimeZone);
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (_sessionCurrencyService.Currency.Equals(Currency.Default))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
convertTasks.Add(Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var convertedCost = _currencyConverter.ConvertAsync(t.Cost,
|
||||||
|
t.Currency, _sessionCurrencyService.Currency,
|
||||||
|
cancellationToken)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
t.Cost = _sessionCurrencyService
|
||||||
|
.Currency.Round(convertedCost);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
foreach (var rad in t.VehicleEnrollment.RouteAddressDetails)
|
||||||
|
{
|
||||||
|
convertTasks.Add(Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var convertedCost = _currencyConverter.ConvertAsync(
|
||||||
|
rad.CostToNextAddress, t.VehicleEnrollment.Currency,
|
||||||
|
_sessionCurrencyService.Currency, cancellationToken)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
rad.CostToNextAddress = _sessionCurrencyService
|
||||||
|
.Currency.Round(convertedCost);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(convertTasks);
|
||||||
|
|
||||||
|
foreach (var t in ticketGroup.Tickets)
|
||||||
|
{
|
||||||
|
if (_sessionCurrencyService.Currency.Equals(Currency.Default))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Currency = _sessionCurrencyService.Currency;
|
||||||
|
t.VehicleEnrollment.Currency = _sessionCurrencyService.Currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
var dto = _mapper.Map<TicketGroupDto>(ticketGroup);
|
||||||
|
|
||||||
|
dto.Currency = _sessionCurrencyService.Currency.Name;
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups.Queries.GetTicketGroup;
|
||||||
|
|
||||||
|
public class GetTicketGroupQueryValidator : AbstractValidator<GetTicketGroupQuery>
|
||||||
|
{
|
||||||
|
public GetTicketGroupQueryValidator(IStringLocalizer localizer)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups.Queries.GetTicketGroupsPage;
|
||||||
|
|
||||||
|
public record GetTicketGroupsPageQuery : IRequest<PaginatedList<TicketGroupDto>>
|
||||||
|
{
|
||||||
|
public int PageNumber { get; set; } = 1;
|
||||||
|
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
|
||||||
|
public string Search { get; set; } = String.Empty;
|
||||||
|
|
||||||
|
public string Sort { get; set; } = String.Empty;
|
||||||
|
|
||||||
|
public HashSet<Sex>? PassangerSex { get; set; }
|
||||||
|
|
||||||
|
public DateOnly? PassangerBirthDateGreaterThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public DateOnly? PassangerBirthDateLessThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset? PurchaseTimeGreaterThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset? PurchaseTimeLessThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public HashSet<TicketStatus>? Statuses { get; set; }
|
||||||
|
|
||||||
|
public HashSet<VehicleType>? VehicleTypes { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan? TravelTimeGreaterThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan? TravelTimeLessThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public Guid? AccountGuid { get; set; }
|
||||||
|
|
||||||
|
// TODO: Add filtering parametetrs listed below. It is hard to
|
||||||
|
// be done because of pagination.
|
||||||
|
|
||||||
|
// public decimal? CostGreaterThanOrEqualTo { get; set; }
|
||||||
|
//
|
||||||
|
// public decimal? CostLessThanOrEqualTo { get; set; }
|
||||||
|
//
|
||||||
|
// public short? NumberOfTransfersGreaterThanOrEqualTo { get; set; }
|
||||||
|
//
|
||||||
|
// public short? NumberOfTransfersLessThanOrEqualTo { get; set; }
|
||||||
|
//
|
||||||
|
// public DateTimeOffset? DepartureTimeGreaterThanOrEqualTo { get; set; }
|
||||||
|
//
|
||||||
|
// public DateTimeOffset? DepartureTimeLessThanOrEqualTo { get; set; }
|
||||||
|
//
|
||||||
|
// public DateTimeOffset? ArrivalTimeGreaterThanOrEqualTo { get; set; }
|
||||||
|
//
|
||||||
|
// public DateTimeOffset? ArrivalTimeLessThanOrEqualTo { get; set; }
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups.Queries.GetTicketGroupsPage;
|
||||||
|
|
||||||
|
public class GetTicketGroupsPageQueryAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<GetTicketGroupsPageQuery>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public GetTicketGroupsPageQueryAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(GetTicketGroupsPageQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles = [IdentityRole.Administrator],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,251 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Extensions;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups.Queries.GetTicketGroupsPage;
|
||||||
|
|
||||||
|
public class GetTicketGroupsPageQueryHandler :
|
||||||
|
IRequestHandler<GetTicketGroupsPageQuery, PaginatedList<TicketGroupDto>>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
private readonly CurrencyConverterService _currencyConverter;
|
||||||
|
|
||||||
|
private readonly SessionCurrencyService _sessionCurrencyService;
|
||||||
|
private readonly SessionTimeZoneService _sessionTimeZoneService;
|
||||||
|
|
||||||
|
private readonly object _lock = new();
|
||||||
|
|
||||||
|
public GetTicketGroupsPageQueryHandler(UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper, CurrencyConverterService currencyConverterService,
|
||||||
|
SessionCurrencyService sessionCurrencyService,
|
||||||
|
SessionTimeZoneService sessionTimeZoneService)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
_currencyConverter = currencyConverterService;
|
||||||
|
_sessionCurrencyService = sessionCurrencyService;
|
||||||
|
_sessionTimeZoneService = sessionTimeZoneService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PaginatedList<TicketGroupDto>> Handle(
|
||||||
|
GetTicketGroupsPageQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var paginatedList = await _unitOfWork.TicketGroupRepository.GetPageAsync(
|
||||||
|
e =>
|
||||||
|
(e.PassangerFirstName.ToLower().Contains(request.Search.ToLower()) ||
|
||||||
|
e.PassangerLastName.ToLower().Contains(request.Search.ToLower()) ||
|
||||||
|
e.PassangerPatronymic.ToLower().Contains(request.Search.ToLower()) ||
|
||||||
|
(e.PassangerEmail != null ?
|
||||||
|
e.PassangerEmail.ToLower().Contains(request.Search.ToLower()) :
|
||||||
|
false)) &&
|
||||||
|
(request.PassangerSex != null
|
||||||
|
? request.PassangerSex.Contains(e.PassangerSex)
|
||||||
|
: true) &&
|
||||||
|
(request.PassangerBirthDateGreaterThanOrEqualTo != null
|
||||||
|
? e.PassangerBirthDate >= request.PassangerBirthDateGreaterThanOrEqualTo
|
||||||
|
: true) &&
|
||||||
|
(request.PassangerBirthDateLessThanOrEqualTo != null
|
||||||
|
? e.PassangerBirthDate <= request.PassangerBirthDateLessThanOrEqualTo
|
||||||
|
: true) &&
|
||||||
|
(request.PurchaseTimeGreaterThanOrEqualTo != null
|
||||||
|
? e.PurchaseTime >= request.PurchaseTimeGreaterThanOrEqualTo
|
||||||
|
: true) &&
|
||||||
|
(request.PurchaseTimeLessThanOrEqualTo != null
|
||||||
|
? e.PurchaseTime <= request.PurchaseTimeLessThanOrEqualTo
|
||||||
|
: true) &&
|
||||||
|
(request.PassangerSex != null
|
||||||
|
? request.PassangerSex.Contains(e.PassangerSex)
|
||||||
|
: true) &&
|
||||||
|
(request.Statuses != null
|
||||||
|
? request.Statuses.Contains(e.Status)
|
||||||
|
: true) &&
|
||||||
|
(request.VehicleTypes != null
|
||||||
|
? e.Tickets
|
||||||
|
.Select(t => t.VehicleEnrollment.Vehicle.VehicleType)
|
||||||
|
.Any(vt => request.VehicleTypes.Contains(vt))
|
||||||
|
: true) &&
|
||||||
|
(request.TravelTimeGreaterThanOrEqualTo != null
|
||||||
|
? e.TravelTime >= request.TravelTimeGreaterThanOrEqualTo
|
||||||
|
: true) &&
|
||||||
|
(request.TravelTimeLessThanOrEqualTo != null
|
||||||
|
? e.TravelTime <= request.TravelTimeLessThanOrEqualTo
|
||||||
|
: true) &&
|
||||||
|
(request.AccountGuid != null
|
||||||
|
? e.Account.Guid == request.AccountGuid
|
||||||
|
: true),
|
||||||
|
e => e.Tickets,
|
||||||
|
request.PageNumber, request.PageSize, cancellationToken);
|
||||||
|
|
||||||
|
var ticketGroups = paginatedList.Items;
|
||||||
|
|
||||||
|
|
||||||
|
// Hydrate
|
||||||
|
|
||||||
|
var vehicleEnrollmentIds =
|
||||||
|
ticketGroups.SelectMany(tg => tg.Tickets)
|
||||||
|
.Select(t => t.VehicleEnrollmentId);
|
||||||
|
var vehicleEnrollments = (await _unitOfWork.VehicleEnrollmentRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
ve => vehicleEnrollmentIds.Contains(ve.Id),
|
||||||
|
ve => ve.Route.RouteAddresses,
|
||||||
|
1, vehicleEnrollmentIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
var routeAddressIds = vehicleEnrollments
|
||||||
|
.SelectMany(ve => ve.Route.RouteAddresses)
|
||||||
|
.Select(ra => ra.Id);
|
||||||
|
var routeAddressDetails = (await _unitOfWork.RouteAddressDetailRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
rad => routeAddressIds.Contains(rad.RouteAddressId),
|
||||||
|
1, routeAddressIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
var addressIds = vehicleEnrollments
|
||||||
|
.SelectMany(ve => ve.Route.RouteAddresses)
|
||||||
|
.Select(ra => ra.AddressId);
|
||||||
|
var addresses = (await _unitOfWork.AddressRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
a => addressIds.Contains(a.Id),
|
||||||
|
a => a.City.Region.Country,
|
||||||
|
1, addressIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
var vehicleIds = vehicleEnrollments
|
||||||
|
.Select(ve => ve.VehicleId);
|
||||||
|
var vehicles = (await _unitOfWork.VehicleRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
v => vehicleIds.Contains(v.Id),
|
||||||
|
v => v.Company,
|
||||||
|
1, vehicleIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
var accountIds =
|
||||||
|
ticketGroups.Select(tg => tg.AccountId);
|
||||||
|
var accounts = (await _unitOfWork.AccountRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
a => accountIds.Contains(a.Id),
|
||||||
|
1, accountIds.Count(), cancellationToken))
|
||||||
|
.Items;
|
||||||
|
|
||||||
|
foreach (var tg in ticketGroups)
|
||||||
|
{
|
||||||
|
if (tg.AccountId != null)
|
||||||
|
{
|
||||||
|
tg.Account = accounts.Single(a => a.Id == tg.AccountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var ve in vehicleEnrollments)
|
||||||
|
{
|
||||||
|
ve.Vehicle = vehicles.Single(v => v.Id == ve.VehicleId);
|
||||||
|
|
||||||
|
foreach (var ra in ve.Route.RouteAddresses)
|
||||||
|
{
|
||||||
|
ra.Address = addresses.Single(a => a.Id == ra.AddressId);
|
||||||
|
ra.Details = routeAddressDetails
|
||||||
|
.Where(rad => rad.RouteAddressId == ra.Id)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Replace with AutoMapper resolvers.
|
||||||
|
// Convert currency and apply session time zone.
|
||||||
|
|
||||||
|
var convertTasks = new List<Task>();
|
||||||
|
var processedRouteAddressDetailIds = new HashSet<long>();
|
||||||
|
|
||||||
|
foreach (var t in ticketGroups.SelectMany(tg => tg.Tickets))
|
||||||
|
{
|
||||||
|
convertTasks.Add(Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
t.VehicleEnrollment.DepartureTime =
|
||||||
|
TimeZoneInfo.ConvertTime(t.VehicleEnrollment.DepartureTime,
|
||||||
|
_sessionTimeZoneService.TimeZone);
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (_sessionCurrencyService.Currency.Equals(Currency.Default))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
convertTasks.Add(Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
var convertedCost = _currencyConverter.ConvertAsync(t.Cost,
|
||||||
|
t.Currency, _sessionCurrencyService.Currency,
|
||||||
|
t.TicketGroup.PurchaseTime, cancellationToken)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
t.Cost = _sessionCurrencyService
|
||||||
|
.Currency.Round(convertedCost);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
foreach (var rad in t.VehicleEnrollment.RouteAddressDetails)
|
||||||
|
{
|
||||||
|
convertTasks.Add(Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (processedRouteAddressDetailIds.Contains(rad.Id))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var convertedCost = _currencyConverter.ConvertAsync(
|
||||||
|
rad.CostToNextAddress, t.VehicleEnrollment.Currency,
|
||||||
|
_sessionCurrencyService.Currency,
|
||||||
|
t.TicketGroup.PurchaseTime, cancellationToken)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
rad.CostToNextAddress = _sessionCurrencyService
|
||||||
|
.Currency.Round(convertedCost);
|
||||||
|
|
||||||
|
processedRouteAddressDetailIds.Add(rad.Id);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(convertTasks);
|
||||||
|
|
||||||
|
foreach (var t in ticketGroups.SelectMany(tg => tg.Tickets))
|
||||||
|
{
|
||||||
|
if (_sessionCurrencyService.Currency.Equals(Currency.Default))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Currency = _sessionCurrencyService.Currency;
|
||||||
|
t.VehicleEnrollment.Currency = _sessionCurrencyService.Currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var mappedItems =
|
||||||
|
_mapper.Map<IEnumerable<TicketGroupDto>>(ticketGroups);
|
||||||
|
|
||||||
|
foreach (var item in mappedItems)
|
||||||
|
{
|
||||||
|
item.Currency = _sessionCurrencyService.Currency.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedItems = QueryableExtension<TicketGroupDto>
|
||||||
|
.ApplySort(mappedItems.AsQueryable(), request.Sort);
|
||||||
|
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return new PaginatedList<TicketGroupDto>(
|
||||||
|
mappedItems.ToList(),
|
||||||
|
paginatedList.TotalCount, request.PageNumber,
|
||||||
|
request.PageSize);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups.Queries.GetTicketGroupsPage;
|
||||||
|
|
||||||
|
public class GetTicketGroupsPageQueryValidator : AbstractValidator<GetTicketGroupsPageQuery>
|
||||||
|
{
|
||||||
|
public GetTicketGroupsPageQueryValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
SessionCultureService cultureService)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.PageNumber)
|
||||||
|
.GreaterThanOrEqualTo(1)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||||
|
1));
|
||||||
|
|
||||||
|
RuleFor(v => v.PageSize)
|
||||||
|
.GreaterThanOrEqualTo(1)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||||
|
1))
|
||||||
|
.LessThanOrEqualTo(50)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.LessThanOrEqualTo"],
|
||||||
|
50));
|
||||||
|
|
||||||
|
RuleFor(v => v.Search)
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
|
|
||||||
|
When(v => v.PassangerSex != null, () =>
|
||||||
|
{
|
||||||
|
RuleForEach(v => v.PassangerSex)
|
||||||
|
.Must((v, s) => Sex.Enumerations.ContainsValue(s))
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
localizer["FluentValidation.MustBeInEnum"],
|
||||||
|
String.Join(
|
||||||
|
", ",
|
||||||
|
Sex.Enumerations.Values.Select(e => e.Name))));
|
||||||
|
});
|
||||||
|
|
||||||
|
When(v => v.Statuses != null, () =>
|
||||||
|
{
|
||||||
|
RuleForEach(v => v.Statuses)
|
||||||
|
.Must((v, s) => TicketStatus.Enumerations.ContainsValue(s))
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
localizer["FluentValidation.MustBeInEnum"],
|
||||||
|
String.Join(
|
||||||
|
", ",
|
||||||
|
TicketStatus.Enumerations.Values.Select(e => e.Name))));
|
||||||
|
});
|
||||||
|
|
||||||
|
When(v => v.VehicleTypes != null, () =>
|
||||||
|
{
|
||||||
|
RuleForEach(v => v.VehicleTypes)
|
||||||
|
.Must((v, s) => VehicleType.Enumerations.ContainsValue(s))
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
localizer["FluentValidation.MustBeInEnum"],
|
||||||
|
String.Join(
|
||||||
|
", ",
|
||||||
|
VehicleType.Enumerations.Values.Select(e => e.Name))));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,53 +0,0 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
|
||||||
using cuqmbr.TravelGuide.Domain.Entities;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.TicketGroups;
|
|
||||||
|
|
||||||
public sealed class TicketAddressDto : IMapFrom<Address>
|
|
||||||
{
|
|
||||||
public Guid Uuid { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public double Longitude { get; set; }
|
|
||||||
|
|
||||||
public double Latitude { get; set; }
|
|
||||||
|
|
||||||
public Guid CountryUuid { get; set; }
|
|
||||||
|
|
||||||
public string CountryName { get; set; }
|
|
||||||
|
|
||||||
public Guid RegionUuid { get; set; }
|
|
||||||
|
|
||||||
public string RegionName { get; set; }
|
|
||||||
|
|
||||||
public Guid CityUuid { get; set; }
|
|
||||||
|
|
||||||
public string CityName { get; set; }
|
|
||||||
|
|
||||||
public void Mapping(MappingProfile profile)
|
|
||||||
{
|
|
||||||
profile.CreateMap<Address, TicketAddressDto>()
|
|
||||||
.ForMember(
|
|
||||||
d => d.Uuid,
|
|
||||||
opt => opt.MapFrom(s => s.Guid))
|
|
||||||
.ForMember(
|
|
||||||
d => d.CountryUuid,
|
|
||||||
opt => opt.MapFrom(s => s.City.Region.Country.Guid))
|
|
||||||
.ForMember(
|
|
||||||
d => d.CountryName,
|
|
||||||
opt => opt.MapFrom(s => s.City.Region.Country.Name))
|
|
||||||
.ForMember(
|
|
||||||
d => d.RegionUuid,
|
|
||||||
opt => opt.MapFrom(s => s.City.Region.Guid))
|
|
||||||
.ForMember(
|
|
||||||
d => d.RegionName,
|
|
||||||
opt => opt.MapFrom(s => s.City.Region.Name))
|
|
||||||
.ForMember(
|
|
||||||
d => d.CityUuid,
|
|
||||||
opt => opt.MapFrom(s => s.City.Guid))
|
|
||||||
.ForMember(
|
|
||||||
d => d.CityName,
|
|
||||||
opt => opt.MapFrom(s => s.City.Name));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
|
||||||
using cuqmbr.TravelGuide.Domain.Entities;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.TicketGroups;
|
|
||||||
|
|
||||||
public sealed class TicketDto : IMapFrom<Ticket>
|
|
||||||
{
|
|
||||||
public Guid Uuid { get; set; }
|
|
||||||
|
|
||||||
public Guid DepartureRouteAddressUuid { get; set; }
|
|
||||||
|
|
||||||
public Guid ArrivalRouteAddressUuid { get; set; }
|
|
||||||
|
|
||||||
public TicketAddressDto DepartureAddress { get; set; }
|
|
||||||
|
|
||||||
public TicketAddressDto ArrivalAddress { get; set; }
|
|
||||||
|
|
||||||
public short Order { get; set; }
|
|
||||||
|
|
||||||
public Guid VehicleEnrollmentUuid { get; set; }
|
|
||||||
|
|
||||||
// TODO: Add VehicleEnrollment model
|
|
||||||
|
|
||||||
public string Currency { get; set; }
|
|
||||||
|
|
||||||
public decimal Cost { get; set; }
|
|
||||||
|
|
||||||
public void Mapping(MappingProfile profile)
|
|
||||||
{
|
|
||||||
profile.CreateMap<Ticket, TicketDto>()
|
|
||||||
.ForMember(
|
|
||||||
d => d.Uuid,
|
|
||||||
opt => opt.MapFrom(s => s.Guid))
|
|
||||||
.ForMember(
|
|
||||||
d => d.DepartureRouteAddressUuid,
|
|
||||||
opt => opt.MapFrom(s => s.DepartureRouteAddress.Guid))
|
|
||||||
.ForMember(
|
|
||||||
d => d.ArrivalRouteAddressUuid,
|
|
||||||
opt => opt.MapFrom(s => s.ArrivalRouteAddress.Guid))
|
|
||||||
.ForMember(
|
|
||||||
d => d.DepartureAddress,
|
|
||||||
opt => opt.MapFrom(s => s.DepartureRouteAddress.Address))
|
|
||||||
.ForMember(
|
|
||||||
d => d.ArrivalAddress,
|
|
||||||
opt => opt.MapFrom(s => s.ArrivalRouteAddress.Address))
|
|
||||||
.ForMember(
|
|
||||||
d => d.VehicleEnrollmentUuid,
|
|
||||||
opt => opt.MapFrom(s => s.VehicleEnrollment.Guid));
|
|
||||||
}
|
|
||||||
}
|
|
21
src/Application/TicketGroups/TicketGroupAccountDto.cs
Normal file
21
src/Application/TicketGroups/TicketGroupAccountDto.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups;
|
||||||
|
|
||||||
|
public sealed class TicketGroupAccountDto : IMapFrom<Account>
|
||||||
|
{
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public string Email { get; set; }
|
||||||
|
|
||||||
|
public void Mapping(MappingProfile profile)
|
||||||
|
{
|
||||||
|
profile.CreateMap<Account, TicketGroupAccountDto>()
|
||||||
|
.ForMember(
|
||||||
|
d => d.Uuid,
|
||||||
|
opt => opt.MapFrom(s => s.Guid));
|
||||||
|
}
|
||||||
|
}
|
87
src/Application/TicketGroups/TicketGroupAddressDto.cs
Normal file
87
src/Application/TicketGroups/TicketGroupAddressDto.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups;
|
||||||
|
|
||||||
|
public sealed class TicketGroupAddressDto : IMapFrom<RouteAddressDetail>
|
||||||
|
{
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public double Longitude { get; set; }
|
||||||
|
|
||||||
|
public double Latitude { get; set; }
|
||||||
|
|
||||||
|
public Guid CountryUuid { get; set; }
|
||||||
|
|
||||||
|
public string CountryName { get; set; }
|
||||||
|
|
||||||
|
public Guid RegionUuid { get; set; }
|
||||||
|
|
||||||
|
public string RegionName { get; set; }
|
||||||
|
|
||||||
|
public Guid CityUuid { get; set; }
|
||||||
|
|
||||||
|
public string CityName { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan TimeToNextAddress { get; set; }
|
||||||
|
|
||||||
|
public decimal CostToNextAddress { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan CurrentAddressStopTime { get; set; }
|
||||||
|
|
||||||
|
public short Order { get; set; }
|
||||||
|
|
||||||
|
public Guid RouteAddressUuid { get; set; }
|
||||||
|
|
||||||
|
public void Mapping(MappingProfile profile)
|
||||||
|
{
|
||||||
|
profile.CreateMap<RouteAddressDetail, TicketGroupAddressDto>()
|
||||||
|
.ForMember(
|
||||||
|
d => d.Uuid,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Address.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Name,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Address.Name))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Longitude,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Address.Longitude))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Latitude,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Address.Latitude))
|
||||||
|
.ForMember(
|
||||||
|
d => d.CountryUuid,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Address.City.Region.Country.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.CountryName,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Address.City.Region.Country.Name))
|
||||||
|
.ForMember(
|
||||||
|
d => d.RegionUuid,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Address.City.Region.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.RegionName,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Address.City.Region.Name))
|
||||||
|
.ForMember(
|
||||||
|
d => d.CityUuid,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Address.City.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.CityName,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Address.City.Name))
|
||||||
|
.ForMember(
|
||||||
|
d => d.TimeToNextAddress,
|
||||||
|
opt => opt.MapFrom(s => s.TimeToNextAddress))
|
||||||
|
.ForMember(
|
||||||
|
d => d.CostToNextAddress,
|
||||||
|
opt => opt.MapFrom(s => s.CostToNextAddress))
|
||||||
|
.ForMember(
|
||||||
|
d => d.CurrentAddressStopTime,
|
||||||
|
opt => opt.MapFrom(s => s.CurrentAddressStopTime))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Order,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Order))
|
||||||
|
.ForMember(
|
||||||
|
d => d.RouteAddressUuid,
|
||||||
|
opt => opt.MapFrom(s => s.RouteAddress.Guid));
|
||||||
|
}
|
||||||
|
}
|
25
src/Application/TicketGroups/TicketGroupCompanyDto.cs
Normal file
25
src/Application/TicketGroups/TicketGroupCompanyDto.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups;
|
||||||
|
|
||||||
|
public sealed class TicketGroupCompanyDto : IMapFrom<Company>
|
||||||
|
{
|
||||||
|
public string Uuid { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string LegalAddress { get; set; }
|
||||||
|
|
||||||
|
public string ContactEmail { get; set; }
|
||||||
|
|
||||||
|
public string ContactPhoneNumber { get; set; }
|
||||||
|
|
||||||
|
public void Mapping(MappingProfile profile)
|
||||||
|
{
|
||||||
|
profile.CreateMap<Company, TicketGroupCompanyDto>()
|
||||||
|
.ForMember(
|
||||||
|
d => d.Uuid,
|
||||||
|
opt => opt.MapFrom(s => s.Guid));
|
||||||
|
}
|
||||||
|
}
|
@ -18,14 +18,30 @@ public sealed class TicketGroupDto : IMapFrom<TicketGroup>
|
|||||||
|
|
||||||
public DateOnly PassangerBirthDate { get; set; }
|
public DateOnly PassangerBirthDate { get; set; }
|
||||||
|
|
||||||
|
public string? PassangerEmail { get; set; }
|
||||||
|
|
||||||
public DateTimeOffset PurchaseTime { get; set; }
|
public DateTimeOffset PurchaseTime { get; set; }
|
||||||
|
|
||||||
public bool Returned { get; set; }
|
public string Status { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public DateTimeOffset DepartureTime { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset ArrivalTime { get; set; }
|
||||||
|
|
||||||
public TimeSpan TravelTime { get; set; }
|
public TimeSpan TravelTime { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan TimeInStops { get; set; }
|
||||||
|
|
||||||
public ICollection<TicketDto> Tickets { get; set; }
|
public int NumberOfTransfers { get; set; }
|
||||||
|
|
||||||
|
public string Currency { get; set; }
|
||||||
|
|
||||||
|
public decimal Cost { get; set; }
|
||||||
|
|
||||||
|
public TicketGroupAccountDto? Account { get; set; }
|
||||||
|
|
||||||
|
public ICollection<TicketGroupVehicleEnrollmentDto> Enrollments { get; set; }
|
||||||
|
|
||||||
public void Mapping(MappingProfile profile)
|
public void Mapping(MappingProfile profile)
|
||||||
{
|
{
|
||||||
@ -40,6 +56,108 @@ public sealed class TicketGroupDto : IMapFrom<TicketGroup>
|
|||||||
d => d.PurchaseTime,
|
d => d.PurchaseTime,
|
||||||
opt => opt
|
opt => opt
|
||||||
.MapFrom<DateTimeOffsetToLocalResolver, DateTimeOffset>(
|
.MapFrom<DateTimeOffsetToLocalResolver, DateTimeOffset>(
|
||||||
s => s.PurchaseTime));
|
s => s.PurchaseTime))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Status,
|
||||||
|
opt => opt.MapFrom(s => s.Status.Name))
|
||||||
|
.ForMember(
|
||||||
|
d => d.DepartureTime,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
var departureRouteAddressId =
|
||||||
|
s.Tickets
|
||||||
|
.OrderBy(t => t.Order)
|
||||||
|
.First()
|
||||||
|
.DepartureRouteAddressId;
|
||||||
|
return
|
||||||
|
s.Tickets
|
||||||
|
.OrderBy(t => t.Order)
|
||||||
|
.First().VehicleEnrollment
|
||||||
|
.GetDepartureTime(departureRouteAddressId);
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.ArrivalTime,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
var arrivalRouteAddressId =
|
||||||
|
s.Tickets
|
||||||
|
.OrderBy(t => t.Order)
|
||||||
|
.First()
|
||||||
|
.ArrivalRouteAddressId;
|
||||||
|
return
|
||||||
|
s.Tickets
|
||||||
|
.OrderBy(t => t.Order)
|
||||||
|
.First().VehicleEnrollment
|
||||||
|
.GetArrivalTime(arrivalRouteAddressId);
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.TravelTime,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
var departureRouteAddressId =
|
||||||
|
s.Tickets
|
||||||
|
.OrderBy(t => t.Order)
|
||||||
|
.First()
|
||||||
|
.DepartureRouteAddressId;
|
||||||
|
var arrivalRouteAddressId =
|
||||||
|
s.Tickets
|
||||||
|
.OrderBy(t => t.Order)
|
||||||
|
.First()
|
||||||
|
.ArrivalRouteAddressId;
|
||||||
|
var departureTime =
|
||||||
|
s.Tickets
|
||||||
|
.OrderBy(t => t.Order)
|
||||||
|
.First().VehicleEnrollment
|
||||||
|
.GetDepartureTime(departureRouteAddressId);
|
||||||
|
var arrivalTime =
|
||||||
|
s.Tickets
|
||||||
|
.OrderBy(t => t.Order)
|
||||||
|
.First().VehicleEnrollment
|
||||||
|
.GetArrivalTime(departureRouteAddressId);
|
||||||
|
return arrivalTime - departureTime;
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.TimeInStops,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
var timePeriodsInStops =
|
||||||
|
s.Tickets.Select(t =>
|
||||||
|
{
|
||||||
|
var departureRouteAddressId =
|
||||||
|
t.DepartureRouteAddressId;
|
||||||
|
var arrivalRouteAddressId =
|
||||||
|
t.ArrivalRouteAddressId;
|
||||||
|
return
|
||||||
|
t.VehicleEnrollment.GetTimeInStops(
|
||||||
|
departureRouteAddressId,
|
||||||
|
arrivalRouteAddressId);
|
||||||
|
});
|
||||||
|
return
|
||||||
|
timePeriodsInStops
|
||||||
|
.Aggregate(TimeSpan.Zero,
|
||||||
|
(sum, next) => sum += next);
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.NumberOfTransfers,
|
||||||
|
opt => opt.MapFrom(s => s.Tickets.Count() - 1))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Cost,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
var costs =
|
||||||
|
s.Tickets.Select(t => t.Currency.Round(t.Cost));
|
||||||
|
return
|
||||||
|
costs
|
||||||
|
.Aggregate((decimal)0,
|
||||||
|
(sum, next) => sum += next);
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Enrollments,
|
||||||
|
opt => opt.MapFrom(s => s.Tickets));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
95
src/Application/TicketGroups/TicketGroupVehicleDto.cs
Normal file
95
src/Application/TicketGroups/TicketGroupVehicleDto.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups;
|
||||||
|
|
||||||
|
public sealed class TicketGroupVehicleDto : IMapFrom<Vehicle>
|
||||||
|
{
|
||||||
|
public string Uuid { get; set; }
|
||||||
|
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
public string Number { get; set; }
|
||||||
|
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public short Capacity { get; set; }
|
||||||
|
|
||||||
|
public void Mapping(MappingProfile profile)
|
||||||
|
{
|
||||||
|
profile.CreateMap<Vehicle, TicketGroupVehicleDto>()
|
||||||
|
.ForMember(
|
||||||
|
d => d.Uuid,
|
||||||
|
opt => opt.MapFrom(s => s.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Type,
|
||||||
|
opt => opt.MapFrom(s => s.VehicleType.Name))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Number,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
if (s is Bus)
|
||||||
|
{
|
||||||
|
return ((Bus)s).Number;
|
||||||
|
}
|
||||||
|
else if (s is Aircraft)
|
||||||
|
{
|
||||||
|
return ((Aircraft)s).Number;
|
||||||
|
}
|
||||||
|
else if (s is Train)
|
||||||
|
{
|
||||||
|
return ((Train)s).Number;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Model,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
if (s is Bus)
|
||||||
|
{
|
||||||
|
return ((Bus)s).Model;
|
||||||
|
}
|
||||||
|
else if (s is Aircraft)
|
||||||
|
{
|
||||||
|
return ((Aircraft)s).Model;
|
||||||
|
}
|
||||||
|
else if (s is Train)
|
||||||
|
{
|
||||||
|
return ((Train)s).Model;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Capacity,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
if (s is Bus)
|
||||||
|
{
|
||||||
|
return ((Bus)s).Capacity;
|
||||||
|
}
|
||||||
|
else if (s is Aircraft)
|
||||||
|
{
|
||||||
|
return ((Aircraft)s).Capacity;
|
||||||
|
}
|
||||||
|
else if (s is Train)
|
||||||
|
{
|
||||||
|
return ((Train)s).Capacity;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.TicketGroups;
|
||||||
|
|
||||||
|
public sealed class TicketGroupVehicleEnrollmentDto : IMapFrom<Ticket>
|
||||||
|
{
|
||||||
|
public DateTimeOffset DepartureTime { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset ArrivalTime { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan TravelTime { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan TimeMoving { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan TimeInStops { get; set; }
|
||||||
|
|
||||||
|
public int NumberOfStops { get; set; }
|
||||||
|
|
||||||
|
public string Currency { get; set; }
|
||||||
|
|
||||||
|
public decimal Cost { get; set; }
|
||||||
|
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public short Order { get; set; }
|
||||||
|
|
||||||
|
public TicketGroupCompanyDto Company { get; set; }
|
||||||
|
|
||||||
|
public TicketGroupVehicleDto Vehicle { get; set; }
|
||||||
|
|
||||||
|
public ICollection<TicketGroupAddressDto> Addresses { get; set; }
|
||||||
|
|
||||||
|
public void Mapping(MappingProfile profile)
|
||||||
|
{
|
||||||
|
profile.CreateMap<Ticket, TicketGroupVehicleEnrollmentDto>()
|
||||||
|
.ForMember(
|
||||||
|
d => d.Uuid,
|
||||||
|
opt => opt.MapFrom(s => s.VehicleEnrollment.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.DepartureTime,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
return s.VehicleEnrollment
|
||||||
|
.GetDepartureTime(s.DepartureRouteAddressId);
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.ArrivalTime,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
return s.VehicleEnrollment
|
||||||
|
.GetArrivalTime(s.ArrivalRouteAddressId);
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.TravelTime,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
var departureTime = s.VehicleEnrollment
|
||||||
|
.GetDepartureTime(s.DepartureRouteAddressId);
|
||||||
|
var arrivalTime = s.VehicleEnrollment
|
||||||
|
.GetArrivalTime(s.ArrivalRouteAddressId);
|
||||||
|
return arrivalTime - departureTime;
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.TimeInStops,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
return s.VehicleEnrollment.GetTimeInStops(
|
||||||
|
s.DepartureRouteAddressId, s.ArrivalRouteAddressId);
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.NumberOfStops,
|
||||||
|
opt => opt.MapFrom(
|
||||||
|
(s, d) =>
|
||||||
|
{
|
||||||
|
return s.VehicleEnrollment.GetNumberOfStops(
|
||||||
|
s.DepartureRouteAddressId, s.ArrivalRouteAddressId);
|
||||||
|
}))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Currency,
|
||||||
|
opt => opt.MapFrom(s => s.Currency))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Cost,
|
||||||
|
opt => opt.MapFrom(s => s.Currency.Round(s.Cost)))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Company,
|
||||||
|
opt => opt.MapFrom(s => s.VehicleEnrollment.Vehicle.Company))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Vehicle,
|
||||||
|
opt => opt.MapFrom(s => s.VehicleEnrollment.Vehicle))
|
||||||
|
.ForMember(
|
||||||
|
d => d.Addresses,
|
||||||
|
opt => opt.MapFrom(s => s.VehicleEnrollment.RouteAddressDetails));
|
||||||
|
}
|
||||||
|
}
|
@ -2,11 +2,23 @@ namespace cuqmbr.TravelGuide.Application.TicketGroups.ViewModels;
|
|||||||
|
|
||||||
public sealed class GetTicketGroupsPageFilterViewModel
|
public sealed class GetTicketGroupsPageFilterViewModel
|
||||||
{
|
{
|
||||||
public string? Sex { get; set; }
|
public HashSet<string>? PassangerSex { get; set; }
|
||||||
|
|
||||||
public DateOnly? BirthDateGreaterThanOrEqualTo { get; set; }
|
public DateOnly? PassangerBirthDateGreaterThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
public DateOnly? BirthDateLessThanOrEqualTo { get; set; }
|
public DateOnly? PassangerBirthDateLessThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
public Guid? CompanyUuid { get; set; }
|
public DateTimeOffset? PurchaseTimeGreaterThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset? PurchaseTimeLessThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public HashSet<string>? Statuses { get; set; }
|
||||||
|
|
||||||
|
public HashSet<string>? VehicleTypes { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan? TravelTimeGreaterThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public TimeSpan? TravelTimeLessThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public Guid? AccountUuid { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
namespace cuqmbr.TravelGuide.Application.TicketGroups.ViewModels;
|
|
||||||
|
|
||||||
public sealed class UpdateTicketGroupViewModel
|
|
||||||
{
|
|
||||||
public string PassangerFirstName { get; set; }
|
|
||||||
|
|
||||||
public string PassangerLastName { get; set; }
|
|
||||||
|
|
||||||
public string PassangerPatronymic { get; set; }
|
|
||||||
|
|
||||||
public string PassangerSex { get; set; }
|
|
||||||
|
|
||||||
public DateOnly PassangerBirthDate { get; set; }
|
|
||||||
|
|
||||||
public DateTimeOffset PurchaseTime { get; set; }
|
|
||||||
|
|
||||||
public bool Returned { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
public ICollection<TicketViewModel> Tickets { get; set; }
|
|
||||||
}
|
|
@ -357,16 +357,6 @@ public class SearchAllQueryHandler :
|
|||||||
tag = path.Select(e => e.Tag).Last();
|
tag = path.Select(e => e.Tag).Last();
|
||||||
|
|
||||||
|
|
||||||
lastRouteAddressGuid = vehicleEnrollments
|
|
||||||
.Single(e => e.Id == tag.VehicleEnrollmentId)
|
|
||||||
.RouteAddressDetails
|
|
||||||
.Select(e => e.RouteAddress)
|
|
||||||
.OrderBy(e => e.Order)
|
|
||||||
.SkipWhile(e => e.Order != tag.RouteAddress.Order)
|
|
||||||
.Take(2)
|
|
||||||
.ElementAt(1)
|
|
||||||
.Guid;
|
|
||||||
|
|
||||||
costToNextAddress = await _currencyConverterService
|
costToNextAddress = await _currencyConverterService
|
||||||
.ConvertAsync(tag.CostToNextAddress,
|
.ConvertAsync(tag.CostToNextAddress,
|
||||||
tag.VehicleEnrollment.Currency,
|
tag.VehicleEnrollment.Currency,
|
||||||
@ -388,7 +378,7 @@ public class SearchAllQueryHandler :
|
|||||||
CostToNextAddress = 0,
|
CostToNextAddress = 0,
|
||||||
CurrentAddressStopTime = tag.CurrentAddressStopTime,
|
CurrentAddressStopTime = tag.CurrentAddressStopTime,
|
||||||
Order = addressOrder,
|
Order = addressOrder,
|
||||||
RouteAddressUuid = lastRouteAddressGuid
|
RouteAddressUuid = tag.RouteAddress.Guid
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,13 +29,11 @@ public static class Configuration
|
|||||||
configuration.GetSection(
|
configuration.GetSection(
|
||||||
PersistenceConfigurationOptions.SectionName));
|
PersistenceConfigurationOptions.SectionName));
|
||||||
|
|
||||||
services.AddOptions<ApplicationConfigurationOptions>().Bind(
|
services.AddOptions<ApplicationConfigurationOptions>()
|
||||||
configuration.GetSection(
|
.Bind(configuration);
|
||||||
ApplicationConfigurationOptions.SectionName));
|
|
||||||
|
|
||||||
services.AddOptions<InfrastructureConfigurationOptions>().Bind(
|
services.AddOptions<InfrastructureConfigurationOptions>()
|
||||||
configuration.GetSection(
|
.Bind(configuration);
|
||||||
InfrastructureConfigurationOptions.SectionName));
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,10 @@ public static class Configuration
|
|||||||
ExchangeApiCurrencyConverterService>()
|
ExchangeApiCurrencyConverterService>()
|
||||||
.AddScoped<
|
.AddScoped<
|
||||||
cuqmbr.TravelGuide.Application.Common.Services.LiqPayPaymentService,
|
cuqmbr.TravelGuide.Application.Common.Services.LiqPayPaymentService,
|
||||||
cuqmbr.TravelGuide.Infrastructure.Services.LiqPayPaymentService>();
|
cuqmbr.TravelGuide.Infrastructure.Services.LiqPayPaymentService>()
|
||||||
|
.AddScoped<
|
||||||
|
EmailSenderService,
|
||||||
|
MailKitEmailSenderService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
@ -164,11 +164,25 @@
|
|||||||
"Microsoft.Extensions.Options": "8.0.0"
|
"Microsoft.Extensions.Options": "8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"BouncyCastle.Cryptography": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.5.1",
|
||||||
|
"contentHash": "zy8TMeTP+1FH2NrLaNZtdRbBdq7u5MI+NFZQOBSM69u5RFkciinwzV2eveY6Kjf5MzgsYvvl6kTStsj3JrXqkg=="
|
||||||
|
},
|
||||||
"FluentValidation": {
|
"FluentValidation": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "11.11.0",
|
"resolved": "11.11.0",
|
||||||
"contentHash": "cyIVdQBwSipxWG8MA3Rqox7iNbUNUTK5bfJi9tIdm4CAfH71Oo5ABLP4/QyrUwuakqpUEPGtE43BDddvEehuYw=="
|
"contentHash": "cyIVdQBwSipxWG8MA3Rqox7iNbUNUTK5bfJi9tIdm4CAfH71Oo5ABLP4/QyrUwuakqpUEPGtE43BDddvEehuYw=="
|
||||||
},
|
},
|
||||||
|
"MailKit": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "4.12.1",
|
||||||
|
"contentHash": "rIqJm92qtHvk1hDchsJ95Hy7n46A7imE24ol++ikXBsjf3Bi1qDBu4H91FfY6LrYXJaxRlc2gIIpC8AOJrCbqg==",
|
||||||
|
"dependencies": {
|
||||||
|
"MimeKit": "4.12.0",
|
||||||
|
"System.Formats.Asn1": "8.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"MediatR": {
|
"MediatR": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "12.4.1",
|
"resolved": "12.4.1",
|
||||||
@ -701,6 +715,15 @@
|
|||||||
"System.Security.Principal.Windows": "4.5.0"
|
"System.Security.Principal.Windows": "4.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"MimeKit": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "4.12.0",
|
||||||
|
"contentHash": "PFUHfs6BZxKYM/QPJksAwXphbJf0SEfdSfsoQ6p6yvFRaJPofFJMBiotWhFRrdSUzfp6C6K49EjBIqIwZ2TJqA==",
|
||||||
|
"dependencies": {
|
||||||
|
"BouncyCastle.Cryptography": "2.5.1",
|
||||||
|
"System.Security.Cryptography.Pkcs": "8.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Newtonsoft.Json": {
|
"Newtonsoft.Json": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "13.0.3",
|
"resolved": "13.0.3",
|
||||||
@ -769,6 +792,11 @@
|
|||||||
"resolved": "5.0.0",
|
"resolved": "5.0.0",
|
||||||
"contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
|
"contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
|
||||||
},
|
},
|
||||||
|
"System.Formats.Asn1": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "8.0.1",
|
||||||
|
"contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A=="
|
||||||
|
},
|
||||||
"System.IdentityModel.Tokens.Jwt": {
|
"System.IdentityModel.Tokens.Jwt": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.0.1",
|
"resolved": "8.0.1",
|
||||||
@ -845,6 +873,7 @@
|
|||||||
"type": "Project",
|
"type": "Project",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Application": "[1.0.0, )",
|
"Application": "[1.0.0, )",
|
||||||
|
"MailKit": "[4.12.1, )",
|
||||||
"Microsoft.Extensions.Http": "[9.0.4, )",
|
"Microsoft.Extensions.Http": "[9.0.4, )",
|
||||||
"Newtonsoft.Json": "[13.0.3, )"
|
"Newtonsoft.Json": "[13.0.3, )"
|
||||||
}
|
}
|
||||||
|
@ -13,4 +13,11 @@ public sealed class Account : EntityBase
|
|||||||
public ICollection<AccountRole> AccountRoles { get; set; }
|
public ICollection<AccountRole> AccountRoles { get; set; }
|
||||||
|
|
||||||
public ICollection<RefreshToken> RefreshTokens { get; set; }
|
public ICollection<RefreshToken> RefreshTokens { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public Employee? Employee { get; set; }
|
||||||
|
|
||||||
|
public Company? Company { get; set; }
|
||||||
|
|
||||||
|
public ICollection<TicketGroup> TicketGroups { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -14,4 +14,9 @@ public sealed class Company : EntityBase
|
|||||||
public ICollection<Employee> Employees { get; set; }
|
public ICollection<Employee> Employees { get; set; }
|
||||||
|
|
||||||
public ICollection<Vehicle> Vehicles { get; set; }
|
public ICollection<Vehicle> Vehicles { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public long AccountId { get; set; }
|
||||||
|
|
||||||
|
public Account Account { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -22,4 +22,9 @@ public sealed class Employee : EntityBase
|
|||||||
public ICollection<EmployeeDocument> Documents { get; set; }
|
public ICollection<EmployeeDocument> Documents { get; set; }
|
||||||
|
|
||||||
public ICollection<VehicleEnrollmentEmployee> VehicleEnrollmentEmployees { get; set; }
|
public ICollection<VehicleEnrollmentEmployee> VehicleEnrollmentEmployees { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public long AccountId { get; set; }
|
||||||
|
|
||||||
|
public Account Account { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ public sealed class TicketGroup : EntityBase
|
|||||||
|
|
||||||
public DateOnly PassangerBirthDate { get; set; }
|
public DateOnly PassangerBirthDate { get; set; }
|
||||||
|
|
||||||
|
public string? PassangerEmail { get; set; }
|
||||||
|
|
||||||
public DateTimeOffset PurchaseTime { get; set; }
|
public DateTimeOffset PurchaseTime { get; set; }
|
||||||
|
|
||||||
public TicketStatus Status { get; set; }
|
public TicketStatus Status { get; set; }
|
||||||
@ -22,4 +24,9 @@ public sealed class TicketGroup : EntityBase
|
|||||||
|
|
||||||
|
|
||||||
public ICollection<Ticket> Tickets { get; set; }
|
public ICollection<Ticket> Tickets { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public long? AccountId { get; set; }
|
||||||
|
|
||||||
|
public Account? Account { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ public class VehicleEnrollment : EntityBase
|
|||||||
.OrderBy(e => e.RouteAddress.Order);
|
.OrderBy(e => e.RouteAddress.Order);
|
||||||
|
|
||||||
var departureRouteAddressDetail = orderedRouteAddressDetails
|
var departureRouteAddressDetail = orderedRouteAddressDetails
|
||||||
.Single(e => e.Id == DepartureRouteAddressId);
|
.Single(e => e.RouteAddressId == DepartureRouteAddressId);
|
||||||
|
|
||||||
var timeInStops = TimeSpan.Zero;
|
var timeInStops = TimeSpan.Zero;
|
||||||
foreach (var routeAddressDetail in orderedRouteAddressDetails)
|
foreach (var routeAddressDetail in orderedRouteAddressDetails)
|
||||||
@ -159,8 +159,8 @@ public class VehicleEnrollment : EntityBase
|
|||||||
return
|
return
|
||||||
RouteAddressDetails
|
RouteAddressDetails
|
||||||
.OrderBy(e => e.RouteAddress.Order)
|
.OrderBy(e => e.RouteAddress.Order)
|
||||||
.SkipWhile(e => e.Id != DepartureRouteAddressId)
|
.SkipWhile(e => e.RouteAddressId != DepartureRouteAddressId)
|
||||||
.TakeWhile(e => e.Id != ArrivalRouteAddressId)
|
.TakeWhile(e => e.RouteAddressId != ArrivalRouteAddressId)
|
||||||
.Count() - 1;
|
.Count() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,8 +180,8 @@ public class VehicleEnrollment : EntityBase
|
|||||||
return
|
return
|
||||||
RouteAddressDetails
|
RouteAddressDetails
|
||||||
.OrderBy(e => e.RouteAddress.Order)
|
.OrderBy(e => e.RouteAddress.Order)
|
||||||
.SkipWhile(e => e.Id != DepartureRouteAddressId)
|
.SkipWhile(e => e.RouteAddressId != DepartureRouteAddressId)
|
||||||
.TakeWhile(e => e.Id != ArrivalRouteAddressId)
|
.TakeWhile(e => e.RouteAddressId != ArrivalRouteAddressId)
|
||||||
.Aggregate(TimeSpan.Zero,
|
.Aggregate(TimeSpan.Zero,
|
||||||
(sum, next) => sum += next.TimeToNextAddress);
|
(sum, next) => sum += next.TimeToNextAddress);
|
||||||
}
|
}
|
||||||
@ -202,8 +202,8 @@ public class VehicleEnrollment : EntityBase
|
|||||||
return
|
return
|
||||||
RouteAddressDetails
|
RouteAddressDetails
|
||||||
.OrderBy(e => e.RouteAddress.Order)
|
.OrderBy(e => e.RouteAddress.Order)
|
||||||
.SkipWhile(e => e.Id != DepartureRouteAddressId)
|
.SkipWhile(e => e.RouteAddressId != DepartureRouteAddressId)
|
||||||
.TakeWhile(e => e.Id != ArrivalRouteAddressId)
|
.TakeWhile(e => e.RouteAddressId != ArrivalRouteAddressId)
|
||||||
.Aggregate((decimal)0,
|
.Aggregate((decimal)0,
|
||||||
(sum, next) => sum += next.CostToNextAddress);
|
(sum, next) => sum += next.CostToNextAddress);
|
||||||
}
|
}
|
||||||
|
@ -14,24 +14,39 @@ public abstract class Currency : Enumeration<Currency>
|
|||||||
|
|
||||||
protected Currency(int value, string name) : base(value, name) { }
|
protected Currency(int value, string name) : base(value, name) { }
|
||||||
|
|
||||||
|
protected virtual byte DecimalDigits { get; } = byte.MaxValue;
|
||||||
|
|
||||||
|
public decimal Round(decimal amount)
|
||||||
|
{
|
||||||
|
return Math.Round(amount, DecimalDigits);
|
||||||
|
}
|
||||||
|
|
||||||
// When no currency is specified
|
// When no currency is specified
|
||||||
private sealed class DefaultCurrency : Currency
|
private sealed class DefaultCurrency : Currency
|
||||||
{
|
{
|
||||||
public DefaultCurrency() : base(Int32.MaxValue, "DEFAULT") { }
|
public DefaultCurrency() : base(Int32.MaxValue, "DEFAULT") { }
|
||||||
|
|
||||||
|
protected override byte DecimalDigits => 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class USDCurrency : Currency
|
private sealed class USDCurrency : Currency
|
||||||
{
|
{
|
||||||
public USDCurrency() : base(840, "USD") { }
|
public USDCurrency() : base(840, "USD") { }
|
||||||
|
|
||||||
|
protected override byte DecimalDigits => 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class EURCurrency : Currency
|
private sealed class EURCurrency : Currency
|
||||||
{
|
{
|
||||||
public EURCurrency() : base(978, "EUR") { }
|
public EURCurrency() : base(978, "EUR") { }
|
||||||
|
|
||||||
|
protected override byte DecimalDigits => 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class UAHCurrency : Currency
|
private sealed class UAHCurrency : Currency
|
||||||
{
|
{
|
||||||
public UAHCurrency() : base(980, "UAH") { }
|
public UAHCurrency() : base(980, "UAH") { }
|
||||||
|
|
||||||
|
protected override byte DecimalDigits => 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,9 @@ public class CompaniesController : ControllerBase
|
|||||||
LegalAddress = viewModel.LegalAddress,
|
LegalAddress = viewModel.LegalAddress,
|
||||||
ContactEmail = viewModel.ContactEmail,
|
ContactEmail = viewModel.ContactEmail,
|
||||||
ContactPhoneNumber = viewModel.ContactPhoneNumber,
|
ContactPhoneNumber = viewModel.ContactPhoneNumber,
|
||||||
|
Username = viewModel.Username,
|
||||||
|
Email = viewModel.Email,
|
||||||
|
Password = viewModel.Password
|
||||||
},
|
},
|
||||||
cancellationToken));
|
cancellationToken));
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,10 @@ public class EmployeesController : ControllerBase
|
|||||||
Information = e.Information
|
Information = e.Information
|
||||||
|
|
||||||
}).ToArray(),
|
}).ToArray(),
|
||||||
CompanyGuid = viewModel.CompanyUuid
|
CompanyGuid = viewModel.CompanyUuid,
|
||||||
|
Username = viewModel.Username,
|
||||||
|
Email = viewModel.Email,
|
||||||
|
Password = viewModel.Password
|
||||||
},
|
},
|
||||||
cancellationToken));
|
cancellationToken));
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,10 @@ using cuqmbr.TravelGuide.Application.Identity.Roles.Queries.GetRolesPage;
|
|||||||
using cuqmbr.TravelGuide.Application.Identity.Accounts;
|
using cuqmbr.TravelGuide.Application.Identity.Accounts;
|
||||||
using cuqmbr.TravelGuide.Application.Identity.Accounts.ViewModels;
|
using cuqmbr.TravelGuide.Application.Identity.Accounts.ViewModels;
|
||||||
using cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.AddAccount;
|
using cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.AddAccount;
|
||||||
// using cuqmbr.TravelGuide.Application.Identity.Commands.AddIdentity;
|
using cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccountsPage;
|
||||||
// using cuqmbr.TravelGuide.Application.Identity.Queries.GetIdentityPage;
|
using cuqmbr.TravelGuide.Application.Identity.Accounts.Queries.GetAccount;
|
||||||
// using cuqmbr.TravelGuide.Application.Identity.Queries.GetIdentity;
|
using cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.UpdateAccount;
|
||||||
// using cuqmbr.TravelGuide.Application.Identity.Commands.UpdateIdentity;
|
using cuqmbr.TravelGuide.Application.Identity.Accounts.Commands.DeleteAccount;
|
||||||
// using cuqmbr.TravelGuide.Application.Identity.Commands.DeleteIdentity;
|
|
||||||
// using cuqmbr.TravelGuide.Application.Identity.ViewModels;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
||||||
|
|
||||||
@ -78,7 +76,7 @@ public class IdentityController : ControllerBase
|
|||||||
[SwaggerResponse(
|
[SwaggerResponse(
|
||||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||||
typeof(ProblemDetails))]
|
typeof(ProblemDetails))]
|
||||||
public async Task<ActionResult<AccountDto>> Add(
|
public async Task<ActionResult<AccountDto>> AddAccount(
|
||||||
[FromBody] AddAccountViewModel viewModel,
|
[FromBody] AddAccountViewModel viewModel,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@ -87,6 +85,7 @@ public class IdentityController : ControllerBase
|
|||||||
await Mediator.Send(
|
await Mediator.Send(
|
||||||
new AddAccountCommand()
|
new AddAccountCommand()
|
||||||
{
|
{
|
||||||
|
Username = viewModel.Username,
|
||||||
Email = viewModel.Email,
|
Email = viewModel.Email,
|
||||||
Password = viewModel.Password,
|
Password = viewModel.Password,
|
||||||
Roles = viewModel.Roles
|
Roles = viewModel.Roles
|
||||||
@ -96,147 +95,144 @@ public class IdentityController : ControllerBase
|
|||||||
cancellationToken));
|
cancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("accounts")]
|
||||||
|
[SwaggerOperation("Get a list of all accounts")]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status200OK, "Request successful",
|
||||||
|
typeof(PaginatedList<AccountDto>))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||||
|
typeof(HttpValidationProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status403Forbidden,
|
||||||
|
"Not enough privileges to perform an action",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
public async Task<PaginatedList<AccountDto>> GetAccountsPage(
|
||||||
|
[FromQuery] PageQuery pageQuery, [FromQuery] SearchQuery searchQuery,
|
||||||
|
[FromQuery] SortQuery sortQuery,
|
||||||
|
[FromQuery] GetAccountsPageFilterViewModel filterQuery,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return await Mediator.Send(
|
||||||
|
new GetAccountsPageQuery()
|
||||||
|
{
|
||||||
|
PageNumber = pageQuery.PageNumber,
|
||||||
|
PageSize = pageQuery.PageSize,
|
||||||
|
Search = searchQuery.Search,
|
||||||
|
Sort = sortQuery.Sort,
|
||||||
|
Roles = filterQuery.Roles == null ? null :
|
||||||
|
filterQuery.Roles
|
||||||
|
.Select(s => IdentityRole.FromName(s))
|
||||||
|
.ToArray()
|
||||||
|
},
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("accounts/{uuid:guid}")]
|
||||||
|
[SwaggerOperation("Get an account by uuid")]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status200OK, "Request successful", typeof(AccountDto))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||||
|
typeof(HttpValidationProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status403Forbidden,
|
||||||
|
"Not enough privileges to perform an action",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status404NotFound, "Object not found", typeof(ProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
public async Task<AccountDto> GetAccount(
|
||||||
|
[FromRoute] Guid uuid,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return await Mediator.Send(new GetAccountQuery() { Guid = uuid },
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("accounts/{uuid:guid}")]
|
||||||
|
[SwaggerOperation("Update an account")]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status200OK, "Request successful", typeof(AccountDto))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status400BadRequest, "Object already exists",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||||
|
typeof(HttpValidationProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status403Forbidden,
|
||||||
|
"Not enough privileges to perform an action",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status404NotFound, "Object not found",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status404NotFound, "Parent object not found",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
[SwaggerResponse(
|
||||||
|
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||||
|
typeof(ProblemDetails))]
|
||||||
|
public async Task<AccountDto> UpdateAccount(
|
||||||
|
[FromRoute] Guid uuid,
|
||||||
|
[FromBody] UpdateAccountViewModel viewModel,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return await Mediator.Send(
|
||||||
|
new UpdateAccountCommand()
|
||||||
|
{
|
||||||
|
Guid = uuid,
|
||||||
|
Username = viewModel.Username,
|
||||||
|
Email = viewModel.Email,
|
||||||
|
Password = viewModel.Password,
|
||||||
|
Roles = viewModel.Roles == null ? null :
|
||||||
|
viewModel.Roles
|
||||||
|
.Select(s => IdentityRole.FromName(s))
|
||||||
|
.ToArray()
|
||||||
|
},
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
// [HttpPost]
|
[HttpDelete("accounts/{uuid:guid}")]
|
||||||
// [SwaggerOperation("Add an identity")]
|
[SwaggerOperation("Delete an account")]
|
||||||
// [SwaggerResponse(
|
[SwaggerResponse(StatusCodes.Status204NoContent, "Request successful")]
|
||||||
// StatusCodes.Status201Created, "Object successfuly created",
|
[SwaggerResponse(
|
||||||
// typeof(IdentityDto))]
|
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||||
// [SwaggerResponse(
|
typeof(HttpValidationProblemDetails))]
|
||||||
// StatusCodes.Status400BadRequest, "Object already exists",
|
[SwaggerResponse(
|
||||||
// typeof(ProblemDetails))]
|
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||||
// [SwaggerResponse(
|
typeof(ProblemDetails))]
|
||||||
// StatusCodes.Status400BadRequest, "Input data validation error",
|
[SwaggerResponse(
|
||||||
// typeof(HttpValidationProblemDetails))]
|
StatusCodes.Status403Forbidden,
|
||||||
// [SwaggerResponse(
|
"Not enough privileges to perform an action",
|
||||||
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
typeof(ProblemDetails))]
|
||||||
// typeof(ProblemDetails))]
|
[SwaggerResponse(
|
||||||
// [SwaggerResponse(
|
StatusCodes.Status404NotFound, "Object not found",
|
||||||
// StatusCodes.Status403Forbidden,
|
typeof(ProblemDetails))]
|
||||||
// "Not enough privileges to perform an action",
|
[SwaggerResponse(
|
||||||
// typeof(ProblemDetails))]
|
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||||
// [SwaggerResponse(
|
typeof(ProblemDetails))]
|
||||||
// StatusCodes.Status404NotFound, "Parent object not found",
|
public async Task<IActionResult> DeleteAccount(
|
||||||
// typeof(ProblemDetails))]
|
[FromRoute] Guid uuid,
|
||||||
// [SwaggerResponse(
|
CancellationToken cancellationToken)
|
||||||
// StatusCodes.Status500InternalServerError, "Internal server error",
|
{
|
||||||
// typeof(ProblemDetails))]
|
await Mediator.Send(
|
||||||
// public async Task<ActionResult<IdentityDto>> Add(
|
new DeleteAccountCommand() { Guid = uuid },
|
||||||
// [FromBody] AddIdentityViewModel viewModel,
|
cancellationToken);
|
||||||
// CancellationToken cancellationToken)
|
return StatusCode(StatusCodes.Status204NoContent);
|
||||||
// {
|
}
|
||||||
// return StatusCode(
|
|
||||||
// StatusCodes.Status201Created,
|
|
||||||
// await Mediator.Send(
|
|
||||||
// new AddIdentityCommand()
|
|
||||||
// {
|
|
||||||
// Name = viewModel.Name,
|
|
||||||
// Longitude = viewModel.Longitude,
|
|
||||||
// Latitude = viewModel.Latitude,
|
|
||||||
// VehicleType = VehicleType.FromName(viewModel.VehicleType),
|
|
||||||
// CityGuid = viewModel.CityUuid
|
|
||||||
// },
|
|
||||||
// cancellationToken));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// [HttpGet("{uuid:guid}")]
|
|
||||||
// [SwaggerOperation("Get an identity by uuid")]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status200OK, "Request successful", typeof(IdentityDto))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status400BadRequest, "Input data validation error",
|
|
||||||
// typeof(HttpValidationProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status403Forbidden,
|
|
||||||
// "Not enough privileges to perform an action",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status404NotFound, "Object not found", typeof(IdentityDto))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status500InternalServerError, "Internal server error",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// public async Task<IdentityDto> Get(
|
|
||||||
// [FromRoute] Guid uuid,
|
|
||||||
// CancellationToken cancellationToken)
|
|
||||||
// {
|
|
||||||
// return await Mediator.Send(new GetIdentityQuery() { Guid = uuid },
|
|
||||||
// cancellationToken);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// [HttpPut("{uuid:guid}")]
|
|
||||||
// [SwaggerOperation("Update an identity")]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status200OK, "Request successful", typeof(IdentityDto))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status400BadRequest, "Object already exists",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status400BadRequest, "Input data validation error",
|
|
||||||
// typeof(HttpValidationProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status403Forbidden,
|
|
||||||
// "Not enough privileges to perform an action",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status404NotFound, "Object not found", typeof(IdentityDto))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status404NotFound, "Parent object not found",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status500InternalServerError, "Internal server error",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// public async Task<IdentityDto> Update(
|
|
||||||
// [FromRoute] Guid uuid,
|
|
||||||
// [FromBody] UpdateIdentityViewModel viewModel,
|
|
||||||
// CancellationToken cancellationToken)
|
|
||||||
// {
|
|
||||||
// return await Mediator.Send(
|
|
||||||
// new UpdateIdentityCommand()
|
|
||||||
// {
|
|
||||||
// Guid = uuid,
|
|
||||||
// Name = viewModel.Name,
|
|
||||||
// Longitude = viewModel.Longitude,
|
|
||||||
// Latitude = viewModel.Latitude,
|
|
||||||
// VehicleType = VehicleType.FromName(viewModel.VehicleType),
|
|
||||||
// CityGuid = viewModel.CityUuid
|
|
||||||
// },
|
|
||||||
// cancellationToken);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// [HttpDelete("{uuid:guid}")]
|
|
||||||
// [SwaggerOperation("Delete an identity")]
|
|
||||||
// [SwaggerResponse(StatusCodes.Status204NoContent, "Request successful")]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status400BadRequest, "Input data validation error",
|
|
||||||
// typeof(HttpValidationProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status403Forbidden,
|
|
||||||
// "Not enough privileges to perform an action",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status404NotFound, "Object not found",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status500InternalServerError, "Internal server error",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// public async Task<IActionResult> Delete(
|
|
||||||
// [FromRoute] Guid uuid,
|
|
||||||
// CancellationToken cancellationToken)
|
|
||||||
// {
|
|
||||||
// await Mediator.Send(
|
|
||||||
// new DeleteIdentityCommand() { Guid = uuid },
|
|
||||||
// cancellationToken);
|
|
||||||
// return StatusCode(StatusCodes.Status204NoContent);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ public class PaymentController : ControllerBase
|
|||||||
PassangerPatronymic = viewModel.PassangerPatronymic,
|
PassangerPatronymic = viewModel.PassangerPatronymic,
|
||||||
PassangerSex = Sex.FromName(viewModel.PassangerSex),
|
PassangerSex = Sex.FromName(viewModel.PassangerSex),
|
||||||
PassangerBirthDate = viewModel.PassangerBirthDate,
|
PassangerBirthDate = viewModel.PassangerBirthDate,
|
||||||
|
PassangerEmail = viewModel.PassangerEmail,
|
||||||
Tickets = viewModel.Tickets.Select(e =>
|
Tickets = viewModel.Tickets.Select(e =>
|
||||||
new TicketGroupPaymentTicketModel()
|
new TicketGroupPaymentTicketModel()
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using cuqmbr.TravelGuide.Application.Common.Services;
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
||||||
|
|
||||||
@ -9,15 +8,13 @@ namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
|||||||
public class TestsController : ControllerBase
|
public class TestsController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IStringLocalizer _localizer;
|
private readonly IStringLocalizer _localizer;
|
||||||
private readonly UnitOfWork _unitOfWork;
|
private readonly EmailSenderService _emailSender;
|
||||||
|
|
||||||
public TestsController(
|
public TestsController(SessionCultureService cultureService,
|
||||||
SessionCultureService cultureService,
|
IStringLocalizer localizer, EmailSenderService emailSender)
|
||||||
IStringLocalizer localizer,
|
|
||||||
UnitOfWork unitOfWork)
|
|
||||||
{
|
{
|
||||||
_localizer = localizer;
|
_localizer = localizer;
|
||||||
_unitOfWork = unitOfWork;
|
_emailSender = emailSender;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("getLocalizedString/{inputString}")]
|
[HttpGet("getLocalizedString/{inputString}")]
|
||||||
@ -31,19 +28,15 @@ public class TestsController : ControllerBase
|
|||||||
[HttpGet("trigger")]
|
[HttpGet("trigger")]
|
||||||
public async Task Trigger(CancellationToken cancellationToken)
|
public async Task Trigger(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// await _unitOfWork.BusRepository.AddOneAsync(
|
var body =
|
||||||
// new Domain.Entities.Bus()
|
@"Hello, friend!
|
||||||
// {
|
|
||||||
// Number = "AB1234MK",
|
|
||||||
// Model = "This is a fancy bus model",
|
|
||||||
// Capacity = 40
|
|
||||||
// },
|
|
||||||
// cancellationToken);
|
|
||||||
//
|
|
||||||
// await _unitOfWork.SaveAsync(cancellationToken);
|
|
||||||
// _unitOfWork.Dispose();
|
|
||||||
|
|
||||||
var vehicles = await _unitOfWork.VehicleRepository
|
This is my email message for you.
|
||||||
.GetPageAsync(1, 10, cancellationToken);
|
|
||||||
|
--
|
||||||
|
Travel Guide Service
|
||||||
|
";
|
||||||
|
|
||||||
|
await _emailSender.SendAsync(new string[] { "cuqmbr@ya.ru" }, "Test subject", body, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,12 @@ using Swashbuckle.AspNetCore.Annotations;
|
|||||||
using cuqmbr.TravelGuide.Domain.Enums;
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
using cuqmbr.TravelGuide.Application.TicketGroups;
|
using cuqmbr.TravelGuide.Application.TicketGroups;
|
||||||
using cuqmbr.TravelGuide.Application.TicketGroups.Commands.AddTicketGroup;
|
using cuqmbr.TravelGuide.Application.TicketGroups.Commands.AddTicketGroup;
|
||||||
|
using cuqmbr.TravelGuide.Application.TicketGroups.Queries.GetTicketGroup;
|
||||||
|
using cuqmbr.TravelGuide.Application.TicketGroups.Queries.GetTicketGroupsPage;
|
||||||
using cuqmbr.TravelGuide.Application.TicketGroups.ViewModels;
|
using cuqmbr.TravelGuide.Application.TicketGroups.ViewModels;
|
||||||
using cuqmbr.TravelGuide.Application.TicketGroups.Models;
|
using cuqmbr.TravelGuide.Application.TicketGroups.Models;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.ViewModels;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
||||||
|
|
||||||
@ -12,7 +16,7 @@ namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
|||||||
public class TicketGroupsController : ControllerBase
|
public class TicketGroupsController : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[SwaggerOperation("Add a ticketGroup")]
|
[SwaggerOperation("Add a ticket group")]
|
||||||
[SwaggerResponse(
|
[SwaggerResponse(
|
||||||
StatusCodes.Status201Created, "Object successfuly created",
|
StatusCodes.Status201Created, "Object successfuly created",
|
||||||
typeof(TicketGroupDto))]
|
typeof(TicketGroupDto))]
|
||||||
@ -67,149 +71,85 @@ public class TicketGroupsController : ControllerBase
|
|||||||
cancellationToken));
|
cancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
// [HttpGet]
|
[HttpGet]
|
||||||
// [SwaggerOperation("Get a list of all ticketGroups")]
|
[SwaggerOperation("Get a list of all ticket groups")]
|
||||||
// [SwaggerResponse(
|
[SwaggerResponse(
|
||||||
// StatusCodes.Status200OK, "Request successful",
|
StatusCodes.Status200OK, "Request successful",
|
||||||
// typeof(PaginatedList<TicketGroupDto>))]
|
typeof(PaginatedList<TicketGroupDto>))]
|
||||||
// [SwaggerResponse(
|
[SwaggerResponse(
|
||||||
// StatusCodes.Status400BadRequest, "Input data validation error",
|
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||||
// typeof(HttpValidationProblemDetails))]
|
typeof(HttpValidationProblemDetails))]
|
||||||
// [SwaggerResponse(
|
[SwaggerResponse(
|
||||||
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||||
// typeof(ProblemDetails))]
|
typeof(ProblemDetails))]
|
||||||
// [SwaggerResponse(
|
[SwaggerResponse(
|
||||||
// StatusCodes.Status403Forbidden,
|
StatusCodes.Status403Forbidden,
|
||||||
// "Not enough privileges to perform an action",
|
"Not enough privileges to perform an action",
|
||||||
// typeof(ProblemDetails))]
|
typeof(ProblemDetails))]
|
||||||
// [SwaggerResponse(
|
[SwaggerResponse(
|
||||||
// StatusCodes.Status500InternalServerError, "Internal server error",
|
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||||
// typeof(ProblemDetails))]
|
typeof(ProblemDetails))]
|
||||||
// public async Task<PaginatedList<TicketGroupDto>> GetPage(
|
public async Task<PaginatedList<TicketGroupDto>> GetPage(
|
||||||
// [FromQuery] PageQuery pageQuery, [FromQuery] SearchQuery searchQuery,
|
[FromQuery] PageQuery pageQuery, [FromQuery] SearchQuery searchQuery,
|
||||||
// [FromQuery] SortQuery sortQuery,
|
[FromQuery] SortQuery sortQuery,
|
||||||
// [FromQuery] GetTicketGroupsPageFilterViewModel filterQuery,
|
[FromQuery] GetTicketGroupsPageFilterViewModel filterQuery,
|
||||||
// CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
// {
|
{
|
||||||
// return await Mediator.Send(
|
return await Mediator.Send(
|
||||||
// new GetTicketGroupsPageQuery()
|
new GetTicketGroupsPageQuery()
|
||||||
// {
|
{
|
||||||
// PageNumber = pageQuery.PageNumber,
|
PageNumber = pageQuery.PageNumber,
|
||||||
// PageSize = pageQuery.PageSize,
|
PageSize = pageQuery.PageSize,
|
||||||
// Search = searchQuery.Search,
|
Search = searchQuery.Search,
|
||||||
// Sort = sortQuery.Sort,
|
Sort = sortQuery.Sort,
|
||||||
// LongitudeGreaterOrEqualThan =
|
PassangerSex = filterQuery.PassangerSex?
|
||||||
// filterQuery.LongitudeGreaterOrEqualThan,
|
.Select(s => Sex.FromName(s)).ToHashSet(),
|
||||||
// LongitudeLessOrEqualThan =
|
PassangerBirthDateGreaterThanOrEqualTo =
|
||||||
// filterQuery.LongitudeLessOrEqualThan,
|
filterQuery.PassangerBirthDateGreaterThanOrEqualTo,
|
||||||
// LatitudeGreaterOrEqualThan =
|
PassangerBirthDateLessThanOrEqualTo =
|
||||||
// filterQuery.LatitudeGreaterOrEqualThan,
|
filterQuery.PassangerBirthDateLessThanOrEqualTo,
|
||||||
// LatitudeLessOrEqualThan =
|
PurchaseTimeGreaterThanOrEqualTo =
|
||||||
// filterQuery.LatitudeLessOrEqualThan,
|
filterQuery.PurchaseTimeGreaterThanOrEqualTo,
|
||||||
// VehicleType = VehicleType.FromName(filterQuery.VehicleType),
|
PurchaseTimeLessThanOrEqualTo =
|
||||||
// CountryGuid = filterQuery.CountryUuid,
|
filterQuery.PurchaseTimeLessThanOrEqualTo,
|
||||||
// RegionGuid = filterQuery.RegionUuid,
|
Statuses = filterQuery.Statuses?
|
||||||
// CityGuid = filterQuery.CityUuid
|
.Select(s => TicketStatus.FromName(s)).ToHashSet(),
|
||||||
// },
|
VehicleTypes = filterQuery.VehicleTypes?
|
||||||
// cancellationToken);
|
.Select(vt => VehicleType.FromName(vt)).ToHashSet(),
|
||||||
// }
|
TravelTimeGreaterThanOrEqualTo =
|
||||||
//
|
filterQuery.TravelTimeGreaterThanOrEqualTo,
|
||||||
// [HttpGet("{uuid:guid}")]
|
TravelTimeLessThanOrEqualTo =
|
||||||
// [SwaggerOperation("Get a ticketGroup by uuid")]
|
filterQuery.TravelTimeLessThanOrEqualTo,
|
||||||
// [SwaggerResponse(
|
AccountGuid = filterQuery.AccountUuid
|
||||||
// StatusCodes.Status200OK, "Request successful", typeof(TicketGroupDto))]
|
},
|
||||||
// [SwaggerResponse(
|
cancellationToken);
|
||||||
// StatusCodes.Status400BadRequest, "Input data validation error",
|
}
|
||||||
// typeof(HttpValidationProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
[HttpGet("{uuid:guid}")]
|
||||||
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
[SwaggerOperation("Get a ticket group by uuid")]
|
||||||
// typeof(ProblemDetails))]
|
[SwaggerResponse(
|
||||||
// [SwaggerResponse(
|
StatusCodes.Status200OK, "Request successful", typeof(TicketGroupDto))]
|
||||||
// StatusCodes.Status403Forbidden,
|
[SwaggerResponse(
|
||||||
// "Not enough privileges to perform an action",
|
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||||
// typeof(ProblemDetails))]
|
typeof(HttpValidationProblemDetails))]
|
||||||
// [SwaggerResponse(
|
[SwaggerResponse(
|
||||||
// StatusCodes.Status404NotFound, "Object not found", typeof(TicketGroupDto))]
|
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||||
// [SwaggerResponse(
|
typeof(ProblemDetails))]
|
||||||
// StatusCodes.Status500InternalServerError, "Internal server error",
|
[SwaggerResponse(
|
||||||
// typeof(ProblemDetails))]
|
StatusCodes.Status403Forbidden,
|
||||||
// public async Task<TicketGroupDto> Get(
|
"Not enough privileges to perform an action",
|
||||||
// [FromRoute] Guid uuid,
|
typeof(ProblemDetails))]
|
||||||
// CancellationToken cancellationToken)
|
[SwaggerResponse(
|
||||||
// {
|
StatusCodes.Status404NotFound, "Object not found",
|
||||||
// return await Mediator.Send(new GetTicketGroupQuery() { Guid = uuid },
|
typeof(ProblemDetails))]
|
||||||
// cancellationToken);
|
[SwaggerResponse(
|
||||||
// }
|
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||||
//
|
typeof(ProblemDetails))]
|
||||||
// [HttpPut("{uuid:guid}")]
|
public async Task<TicketGroupDto> Get(
|
||||||
// [SwaggerOperation("Update a ticketGroup")]
|
[FromRoute] Guid uuid,
|
||||||
// [SwaggerResponse(
|
CancellationToken cancellationToken)
|
||||||
// StatusCodes.Status200OK, "Request successful", typeof(TicketGroupDto))]
|
{
|
||||||
// [SwaggerResponse(
|
return await Mediator.Send(new GetTicketGroupQuery() { Guid = uuid },
|
||||||
// StatusCodes.Status400BadRequest, "Object already exists",
|
cancellationToken);
|
||||||
// typeof(ProblemDetails))]
|
}
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status400BadRequest, "Input data validation error",
|
|
||||||
// typeof(HttpValidationProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status403Forbidden,
|
|
||||||
// "Not enough privileges to perform an action",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status404NotFound, "Object not found", typeof(TicketGroupDto))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status404NotFound, "Parent object not found",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status500InternalServerError, "Internal server error",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// public async Task<TicketGroupDto> Update(
|
|
||||||
// [FromRoute] Guid uuid,
|
|
||||||
// [FromBody] UpdateTicketGroupViewModel viewModel,
|
|
||||||
// CancellationToken cancellationToken)
|
|
||||||
// {
|
|
||||||
// return await Mediator.Send(
|
|
||||||
// new UpdateTicketGroupCommand()
|
|
||||||
// {
|
|
||||||
// Guid = uuid,
|
|
||||||
// Name = viewModel.Name,
|
|
||||||
// Longitude = viewModel.Longitude,
|
|
||||||
// Latitude = viewModel.Latitude,
|
|
||||||
// VehicleType = VehicleType.FromName(viewModel.VehicleType),
|
|
||||||
// CityGuid = viewModel.CityUuid
|
|
||||||
// },
|
|
||||||
// cancellationToken);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// [HttpDelete("{uuid:guid}")]
|
|
||||||
// [SwaggerOperation("Delete a ticketGroup")]
|
|
||||||
// [SwaggerResponse(StatusCodes.Status204NoContent, "Request successful")]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status400BadRequest, "Input data validation error",
|
|
||||||
// typeof(HttpValidationProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status403Forbidden,
|
|
||||||
// "Not enough privileges to perform an action",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status404NotFound, "Object not found",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// [SwaggerResponse(
|
|
||||||
// StatusCodes.Status500InternalServerError, "Internal server error",
|
|
||||||
// typeof(ProblemDetails))]
|
|
||||||
// public async Task<IActionResult> Delete(
|
|
||||||
// [FromRoute] Guid uuid,
|
|
||||||
// CancellationToken cancellationToken)
|
|
||||||
// {
|
|
||||||
// await Mediator.Send(
|
|
||||||
// new DeleteTicketGroupCommand() { Guid = uuid },
|
|
||||||
// cancellationToken);
|
|
||||||
// return StatusCode(StatusCodes.Status204NoContent);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,42 @@
|
|||||||
{
|
{
|
||||||
"Application": {
|
"Logging": {
|
||||||
"Logging": {
|
"Type": "SimpleConsole",
|
||||||
"Type": "SimpleConsole",
|
"LogLevel": "Information",
|
||||||
"LogLevel": "Information",
|
"TimestampFormat": "yyyy-MM-ddTHH:mm:ss.fffK",
|
||||||
"TimestampFormat": "yyyy-MM-ddTHH:mm:ss.fffK",
|
"UseUtcTimestamp": true
|
||||||
"UseUtcTimestamp": true
|
},
|
||||||
},
|
"Localization": {
|
||||||
"Datastore": {
|
"DefaultCultureName": "en-US",
|
||||||
"Type": "postgresql",
|
"CacheDuration": "00:30:00"
|
||||||
"ConnectionString": "Host=127.0.0.1:5432;Database=travel_guide;Username=postgres;Password=0000;Include Error Detail=true"
|
},
|
||||||
},
|
"JsonWebToken": {
|
||||||
"Localization": {
|
"Issuer": "https://api.travel-guide.cuqmbr.xyz",
|
||||||
"DefaultCultureName": "en-US",
|
"Audience": "https://travel-guide.cuqmbr.xyz",
|
||||||
"CacheDuration": "00:30:00"
|
"IssuerSigningKey": "a2c98dec80787a4e85ffb5bcbc24f7e4cc014d8a4fe43e9520480a50759164bc",
|
||||||
},
|
"AccessTokenValidity": "24:00:00",
|
||||||
"JsonWebToken": {
|
"RefreshTokenValidity": "72:00:00"
|
||||||
"Issuer": "https://api.travel-guide.cuqmbr.xyz",
|
},
|
||||||
"Audience": "https://travel-guide.cuqmbr.xyz",
|
"Datastore": {
|
||||||
"IssuerSigningKey": "a2c98dec80787a4e85ffb5bcbc24f7e4cc014d8a4fe43e9520480a50759164bc",
|
"Type": "postgresql",
|
||||||
"AccessTokenValidity": "24:00:00",
|
"ConnectionString": "Host=127.0.0.1:5432;Database=travel_guide;Username=postgres;Password=0000;Include Error Detail=true"
|
||||||
"RefreshTokenValidity": "72:00:00"
|
},
|
||||||
},
|
"PaymentProcessing": {
|
||||||
"Infrastructure": {
|
"CallbackAddressBase": "https://api.travel-guide.cuqmbr.xyz",
|
||||||
"PaymentProcessing": {
|
"ResultAddressBase": "https://travel-guide.cuqmbr.xyz",
|
||||||
"CallbackAddressBase": "https://api.travel-guide.cuqmbr.xyz",
|
"LiqPay": {
|
||||||
"ResultAddressBase": "https://travel-guide.cuqmbr.xyz",
|
"PublicKey": "sandbox_xxxxxxxxxxxx",
|
||||||
"LiqPay": {
|
"PrivateKey": "sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
"PublicKey": "sandbox_xxxxxxxxxxxx",
|
}
|
||||||
"PrivateKey": "sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
},
|
||||||
}
|
"Email": {
|
||||||
}
|
"Smtp": {
|
||||||
|
"Host": "mail.travel-guide.cuqmbr.xyz",
|
||||||
|
"Port": "465",
|
||||||
|
"UseTls": true,
|
||||||
|
"Username": "no-reply",
|
||||||
|
"Password": "super-secret-password",
|
||||||
|
"SenderAddress": "no-reply@travel-guide.cuqmbr.xyz",
|
||||||
|
"SenderName": "Travel Guide"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,42 @@
|
|||||||
{
|
{
|
||||||
"Application": {
|
"Logging": {
|
||||||
"Logging": {
|
"Type": "SimpleConsole",
|
||||||
"Type": "SimpleConsole",
|
"LogLevel": "Information",
|
||||||
"LogLevel": "Information",
|
"TimestampFormat": "yyyy-MM-ddTHH:mm:ss.fffK",
|
||||||
"TimestampFormat": "yyyy-MM-ddTHH:mm:ss.fffK",
|
"UseUtcTimestamp": true
|
||||||
"UseUtcTimestamp": true
|
},
|
||||||
},
|
"Localization": {
|
||||||
"Datastore": {
|
"DefaultCultureName": "en-US",
|
||||||
"Type": "postgresql",
|
"CacheDuration": "00:30:00"
|
||||||
"ConnectionString": "Host=127.0.0.1:5432;Database=travel_guide;Username=postgres;Password=0000;Include Error Detail=true"
|
},
|
||||||
},
|
"JsonWebToken": {
|
||||||
"Localization": {
|
"Issuer": "https://api.travel-guide.cuqmbr.xyz",
|
||||||
"DefaultCultureName": "en-US",
|
"Audience": "https://travel-guide.cuqmbr.xyz",
|
||||||
"CacheDuration": "00:30:00"
|
"IssuerSigningKey": "a2c98dec80787a4e85ffb5bcbc24f7e4cc014d8a4fe43e9520480a50759164bc",
|
||||||
},
|
"AccessTokenValidity": "24:00:00",
|
||||||
"JsonWebToken": {
|
"RefreshTokenValidity": "72:00:00"
|
||||||
"Issuer": "https://api.travel-guide.cuqmbr.xyz",
|
},
|
||||||
"Audience": "https://travel-guide.cuqmbr.xyz",
|
"Datastore": {
|
||||||
"IssuerSigningKey": "a2c98dec80787a4e85ffb5bcbc24f7e4cc014d8a4fe43e9520480a50759164bc",
|
"Type": "postgresql",
|
||||||
"AccessTokenValidity": "24:00:00",
|
"ConnectionString": "Host=127.0.0.1:5432;Database=travel_guide;Username=postgres;Password=0000;Include Error Detail=true"
|
||||||
"RefreshTokenValidity": "72:00:00"
|
},
|
||||||
},
|
"PaymentProcessing": {
|
||||||
"Infrastructure": {
|
"CallbackAddressBase": "https://api.travel-guide.cuqmbr.xyz",
|
||||||
"PaymentProcessing": {
|
"ResultAddressBase": "https://travel-guide.cuqmbr.xyz",
|
||||||
"CallbackAddressBase": "https://api.travel-guide.cuqmbr.xyz",
|
"LiqPay": {
|
||||||
"ResultAddressBase": "https://travel-guide.cuqmbr.xyz",
|
"PublicKey": "sandbox_xxxxxxxxxxxx",
|
||||||
"LiqPay": {
|
"PrivateKey": "sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
"PublicKey": "sandbox_xxxxxxxxxxxx",
|
}
|
||||||
"PrivateKey": "sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
},
|
||||||
}
|
"Email": {
|
||||||
}
|
"Smtp": {
|
||||||
|
"Host": "mail.travel-guide.cuqmbr.xyz",
|
||||||
|
"Port": "465",
|
||||||
|
"UseTls": true,
|
||||||
|
"Username": "no-reply",
|
||||||
|
"Password": "super-secret-password",
|
||||||
|
"SenderAddress": "no-reply@travel-guide.cuqmbr.xyz",
|
||||||
|
"SenderName": "Travel Guide"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,11 @@
|
|||||||
"Microsoft.Extensions.Options": "8.0.0"
|
"Microsoft.Extensions.Options": "8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"BouncyCastle.Cryptography": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.5.1",
|
||||||
|
"contentHash": "zy8TMeTP+1FH2NrLaNZtdRbBdq7u5MI+NFZQOBSM69u5RFkciinwzV2eveY6Kjf5MzgsYvvl6kTStsj3JrXqkg=="
|
||||||
|
},
|
||||||
"FluentValidation": {
|
"FluentValidation": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "11.11.0",
|
"resolved": "11.11.0",
|
||||||
@ -125,6 +130,15 @@
|
|||||||
"resolved": "2.14.1",
|
"resolved": "2.14.1",
|
||||||
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
|
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
|
||||||
},
|
},
|
||||||
|
"MailKit": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "4.12.1",
|
||||||
|
"contentHash": "rIqJm92qtHvk1hDchsJ95Hy7n46A7imE24ol++ikXBsjf3Bi1qDBu4H91FfY6LrYXJaxRlc2gIIpC8AOJrCbqg==",
|
||||||
|
"dependencies": {
|
||||||
|
"MimeKit": "4.12.0",
|
||||||
|
"System.Formats.Asn1": "8.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"MediatR": {
|
"MediatR": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "12.4.1",
|
"resolved": "12.4.1",
|
||||||
@ -848,6 +862,15 @@
|
|||||||
"System.Security.Principal.Windows": "4.5.0"
|
"System.Security.Principal.Windows": "4.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"MimeKit": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "4.12.0",
|
||||||
|
"contentHash": "PFUHfs6BZxKYM/QPJksAwXphbJf0SEfdSfsoQ6p6yvFRaJPofFJMBiotWhFRrdSUzfp6C6K49EjBIqIwZ2TJqA==",
|
||||||
|
"dependencies": {
|
||||||
|
"BouncyCastle.Cryptography": "2.5.1",
|
||||||
|
"System.Security.Cryptography.Pkcs": "8.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Mono.TextTemplating": {
|
"Mono.TextTemplating": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.0.0",
|
"resolved": "3.0.0",
|
||||||
@ -982,6 +1005,11 @@
|
|||||||
"System.Composition.Runtime": "7.0.0"
|
"System.Composition.Runtime": "7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"System.Formats.Asn1": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "8.0.1",
|
||||||
|
"contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A=="
|
||||||
|
},
|
||||||
"System.IdentityModel.Tokens.Jwt": {
|
"System.IdentityModel.Tokens.Jwt": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.0.1",
|
"resolved": "8.0.1",
|
||||||
@ -1109,6 +1137,7 @@
|
|||||||
"type": "Project",
|
"type": "Project",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Application": "[1.0.0, )",
|
"Application": "[1.0.0, )",
|
||||||
|
"MailKit": "[4.12.1, )",
|
||||||
"Microsoft.Extensions.Http": "[9.0.4, )",
|
"Microsoft.Extensions.Http": "[9.0.4, )",
|
||||||
"Newtonsoft.Json": "[13.0.3, )"
|
"Newtonsoft.Json": "[13.0.3, )"
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@ namespace cuqmbr.TravelGuide.Infrastructure;
|
|||||||
|
|
||||||
public sealed class ConfigurationOptions
|
public sealed class ConfigurationOptions
|
||||||
{
|
{
|
||||||
public static string SectionName { get; } = "Application:Infrastructure";
|
public static string SectionName { get; } = "";
|
||||||
|
|
||||||
public PaymentProcessingConfigurationOptions PaymentProcessing { get; set; }
|
public PaymentProcessingConfigurationOptions PaymentProcessing { get; set; } = new();
|
||||||
|
|
||||||
|
public EmailConfigurationOptions Email { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PaymentProcessingConfigurationOptions
|
public sealed class PaymentProcessingConfigurationOptions
|
||||||
@ -13,7 +15,7 @@ public sealed class PaymentProcessingConfigurationOptions
|
|||||||
|
|
||||||
public string ResultAddressBase { get; set; }
|
public string ResultAddressBase { get; set; }
|
||||||
|
|
||||||
public LiqPayConfigurationOptions LiqPay { get; set; }
|
public LiqPayConfigurationOptions LiqPay { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class LiqPayConfigurationOptions
|
public sealed class LiqPayConfigurationOptions
|
||||||
@ -22,3 +24,25 @@ public sealed class LiqPayConfigurationOptions
|
|||||||
|
|
||||||
public string PrivateKey { get; set; }
|
public string PrivateKey { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class EmailConfigurationOptions
|
||||||
|
{
|
||||||
|
public SmtpConfigurationOptions Smtp { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SmtpConfigurationOptions
|
||||||
|
{
|
||||||
|
public string Host { get; set; }
|
||||||
|
|
||||||
|
public ushort Port { get; set; }
|
||||||
|
|
||||||
|
public bool UseTls { get; set; }
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
public string SenderAddress { get; set; }
|
||||||
|
|
||||||
|
public string SenderName { get; set; }
|
||||||
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MailKit" Version="4.12.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.4" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.4" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
50
src/Infrastructure/Services/MailKitEmailSenderService.cs
Normal file
50
src/Infrastructure/Services/MailKitEmailSenderService.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using MailKit.Net.Smtp;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using MimeKit;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Infrastructure.Services;
|
||||||
|
|
||||||
|
public sealed class MailKitEmailSenderService : EmailSenderService
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly SmtpConfigurationOptions _configuration;
|
||||||
|
|
||||||
|
public MailKitEmailSenderService(
|
||||||
|
IOptions<ConfigurationOptions> configuration)
|
||||||
|
{
|
||||||
|
_configuration = configuration.Value.Email.Smtp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendAsync(string[] addresses, string subject,
|
||||||
|
string body, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var message = new MimeMessage();
|
||||||
|
|
||||||
|
message.From.Add(new MailboxAddress(
|
||||||
|
_configuration.SenderName, _configuration.SenderAddress));
|
||||||
|
foreach (var address in addresses)
|
||||||
|
{
|
||||||
|
message.To.Add(new MailboxAddress("", address));
|
||||||
|
}
|
||||||
|
message.Subject = subject;
|
||||||
|
|
||||||
|
message.Body = new TextPart("plain")
|
||||||
|
{
|
||||||
|
Text = body
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
using var client = new SmtpClient();
|
||||||
|
|
||||||
|
await client.ConnectAsync(_configuration.Host,
|
||||||
|
_configuration.Port, _configuration.UseTls,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
await client.AuthenticateAsync(_configuration.Username,
|
||||||
|
_configuration.Password, cancellationToken);
|
||||||
|
|
||||||
|
await client.SendAsync(message, cancellationToken);
|
||||||
|
await client.DisconnectAsync(true, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,16 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"net9.0": {
|
"net9.0": {
|
||||||
|
"MailKit": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[4.12.1, )",
|
||||||
|
"resolved": "4.12.1",
|
||||||
|
"contentHash": "rIqJm92qtHvk1hDchsJ95Hy7n46A7imE24ol++ikXBsjf3Bi1qDBu4H91FfY6LrYXJaxRlc2gIIpC8AOJrCbqg==",
|
||||||
|
"dependencies": {
|
||||||
|
"MimeKit": "4.12.0",
|
||||||
|
"System.Formats.Asn1": "8.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.Extensions.Http": {
|
"Microsoft.Extensions.Http": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[9.0.4, )",
|
"requested": "[9.0.4, )",
|
||||||
@ -40,6 +50,11 @@
|
|||||||
"Microsoft.Extensions.Options": "8.0.0"
|
"Microsoft.Extensions.Options": "8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"BouncyCastle.Cryptography": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "2.5.1",
|
||||||
|
"contentHash": "zy8TMeTP+1FH2NrLaNZtdRbBdq7u5MI+NFZQOBSM69u5RFkciinwzV2eveY6Kjf5MzgsYvvl6kTStsj3JrXqkg=="
|
||||||
|
},
|
||||||
"FluentValidation": {
|
"FluentValidation": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "11.11.0",
|
"resolved": "11.11.0",
|
||||||
@ -289,11 +304,25 @@
|
|||||||
"Microsoft.IdentityModel.Logging": "8.11.0"
|
"Microsoft.IdentityModel.Logging": "8.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"MimeKit": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "4.12.0",
|
||||||
|
"contentHash": "PFUHfs6BZxKYM/QPJksAwXphbJf0SEfdSfsoQ6p6yvFRaJPofFJMBiotWhFRrdSUzfp6C6K49EjBIqIwZ2TJqA==",
|
||||||
|
"dependencies": {
|
||||||
|
"BouncyCastle.Cryptography": "2.5.1",
|
||||||
|
"System.Security.Cryptography.Pkcs": "8.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"QuikGraph": {
|
"QuikGraph": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.5.0",
|
"resolved": "2.5.0",
|
||||||
"contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw=="
|
"contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw=="
|
||||||
},
|
},
|
||||||
|
"System.Formats.Asn1": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "8.0.1",
|
||||||
|
"contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A=="
|
||||||
|
},
|
||||||
"System.IdentityModel.Tokens.Jwt": {
|
"System.IdentityModel.Tokens.Jwt": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.0.1",
|
"resolved": "8.0.1",
|
||||||
@ -308,6 +337,11 @@
|
|||||||
"resolved": "1.6.2",
|
"resolved": "1.6.2",
|
||||||
"contentHash": "piIcdelf4dGotuIjFlyu7JLIZkYTmYM0ZTLGpCcxs9iCJFflhJht0nchkIV+GS5wfA3OtC3QNjIcUqyOdBdOsA=="
|
"contentHash": "piIcdelf4dGotuIjFlyu7JLIZkYTmYM0ZTLGpCcxs9iCJFflhJht0nchkIV+GS5wfA3OtC3QNjIcUqyOdBdOsA=="
|
||||||
},
|
},
|
||||||
|
"System.Security.Cryptography.Pkcs": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "8.0.1",
|
||||||
|
"contentHash": "CoCRHFym33aUSf/NtWSVSZa99dkd0Hm7OCZUxORBjRB16LNhIEOf8THPqzIYlvKM0nNDAPTRBa1FxEECrgaxxA=="
|
||||||
|
},
|
||||||
"application": {
|
"application": {
|
||||||
"type": "Project",
|
"type": "Project",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -2,7 +2,7 @@ namespace cuqmbr.TravelGuide.Persistence;
|
|||||||
|
|
||||||
public sealed class ConfigurationOptions
|
public sealed class ConfigurationOptions
|
||||||
{
|
{
|
||||||
public static string SectionName { get; } = "Application:Datastore";
|
public static string SectionName { get; } = "Datastore";
|
||||||
|
|
||||||
public string Type { get; set; } = "inmemory";
|
public string Type { get; set; } = "inmemory";
|
||||||
|
|
||||||
|
@ -38,5 +38,29 @@ public class CompanyConfiguration : BaseConfiguration<Company>
|
|||||||
.HasColumnName("contact_phone_number")
|
.HasColumnName("contact_phone_number")
|
||||||
.HasColumnType("varchar(64)")
|
.HasColumnType("varchar(64)")
|
||||||
.IsRequired(true);
|
.IsRequired(true);
|
||||||
|
|
||||||
|
|
||||||
|
builder
|
||||||
|
.Property(c => c.AccountId)
|
||||||
|
.HasColumnName("account_id")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.IsRequired(true);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.HasOne(c => c.Account)
|
||||||
|
.WithOne(a => a.Company)
|
||||||
|
.HasForeignKey<Company>(c => c.AccountId)
|
||||||
|
.HasConstraintName(
|
||||||
|
"fk_" +
|
||||||
|
$"{builder.Metadata.GetTableName()}_" +
|
||||||
|
$"{builder.Property(c => c.AccountId).Metadata.GetColumnName()}")
|
||||||
|
.OnDelete(DeleteBehavior.ClientNoAction);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.HasIndex(c => c.AccountId)
|
||||||
|
.HasDatabaseName(
|
||||||
|
"ix_" +
|
||||||
|
$"{builder.Metadata.GetTableName()}_" +
|
||||||
|
$"{builder.Property(c => c.AccountId).Metadata.GetColumnName()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ public class EmployeeConfiguration : BaseConfiguration<Employee>
|
|||||||
"fk_" +
|
"fk_" +
|
||||||
$"{builder.Metadata.GetTableName()}_" +
|
$"{builder.Metadata.GetTableName()}_" +
|
||||||
$"{builder.Property(e => e.CompanyId).Metadata.GetColumnName()}")
|
$"{builder.Property(e => e.CompanyId).Metadata.GetColumnName()}")
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.ClientNoAction);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.HasIndex(e => e.CompanyId)
|
.HasIndex(e => e.CompanyId)
|
||||||
@ -77,5 +77,29 @@ public class EmployeeConfiguration : BaseConfiguration<Employee>
|
|||||||
"ix_" +
|
"ix_" +
|
||||||
$"{builder.Metadata.GetTableName()}_" +
|
$"{builder.Metadata.GetTableName()}_" +
|
||||||
$"{builder.Property(e => e.CompanyId).Metadata.GetColumnName()}");
|
$"{builder.Property(e => e.CompanyId).Metadata.GetColumnName()}");
|
||||||
|
|
||||||
|
|
||||||
|
builder
|
||||||
|
.Property(e => e.AccountId)
|
||||||
|
.HasColumnName("account_id")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.IsRequired(true);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.HasOne(e => e.Account)
|
||||||
|
.WithOne(a => a.Employee)
|
||||||
|
.HasForeignKey<Employee>(e => e.AccountId)
|
||||||
|
.HasConstraintName(
|
||||||
|
"fk_" +
|
||||||
|
$"{builder.Metadata.GetTableName()}_" +
|
||||||
|
$"{builder.Property(e => e.AccountId).Metadata.GetColumnName()}")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.HasIndex(e => e.AccountId)
|
||||||
|
.HasDatabaseName(
|
||||||
|
"ix_" +
|
||||||
|
$"{builder.Metadata.GetTableName()}_" +
|
||||||
|
$"{builder.Property(e => e.AccountId).Metadata.GetColumnName()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,39 +49,69 @@ public class TicketGroupConfiguration : BaseConfiguration<TicketGroup>
|
|||||||
|
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.Property(a => a.PassangerFirstName)
|
.Property(tg => tg.PassangerFirstName)
|
||||||
.HasColumnName("passanger_first_name")
|
.HasColumnName("passanger_first_name")
|
||||||
.HasColumnType("varchar(32)")
|
.HasColumnType("varchar(32)")
|
||||||
.IsRequired(true);
|
.IsRequired(true);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.Property(a => a.PassangerLastName)
|
.Property(tg => tg.PassangerLastName)
|
||||||
.HasColumnName("passanger_last_name")
|
.HasColumnName("passanger_last_name")
|
||||||
.HasColumnType("varchar(32)")
|
.HasColumnType("varchar(32)")
|
||||||
.IsRequired(true);
|
.IsRequired(true);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.Property(a => a.PassangerPatronymic)
|
.Property(tg => tg.PassangerPatronymic)
|
||||||
.HasColumnName("passanger_patronymic")
|
.HasColumnName("passanger_patronymic")
|
||||||
.HasColumnType("varchar(32)")
|
.HasColumnType("varchar(32)")
|
||||||
.IsRequired(true);
|
.IsRequired(true);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.Property(a => a.PassangerBirthDate)
|
.Property(tg => tg.PassangerBirthDate)
|
||||||
.HasColumnName("passanger_birth_date")
|
.HasColumnName("passanger_birth_date")
|
||||||
.HasColumnType("date")
|
.HasColumnType("date")
|
||||||
.IsRequired(true);
|
.IsRequired(true);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.Property(a => a.PurchaseTime)
|
.Property(tg => tg.PassangerEmail)
|
||||||
|
.HasColumnName("passanger_email")
|
||||||
|
.HasColumnType("varchar(256)")
|
||||||
|
.IsRequired(false);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.Property(tg => tg.PurchaseTime)
|
||||||
.HasColumnName("purchase_time")
|
.HasColumnName("purchase_time")
|
||||||
.HasColumnType("timestamptz")
|
.HasColumnType("timestamptz")
|
||||||
.IsRequired(true);
|
.IsRequired(true);
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.Property(a => a.TravelTime)
|
.Property(tg => tg.TravelTime)
|
||||||
.HasColumnName("travel_time")
|
.HasColumnName("travel_time")
|
||||||
.HasColumnType("interval")
|
.HasColumnType("interval")
|
||||||
.IsRequired(true);
|
.IsRequired(true);
|
||||||
|
|
||||||
|
|
||||||
|
builder
|
||||||
|
.Property(tg => tg.AccountId)
|
||||||
|
.HasColumnName("account_id")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.IsRequired(false);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.HasOne(tg => tg.Account)
|
||||||
|
.WithMany(a => a.TicketGroups)
|
||||||
|
.HasForeignKey(tg => tg.AccountId)
|
||||||
|
.HasConstraintName(
|
||||||
|
"fk_" +
|
||||||
|
$"{builder.Metadata.GetTableName()}_" +
|
||||||
|
$"{builder.Property(c => c.AccountId).Metadata.GetColumnName()}")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
builder
|
||||||
|
.HasIndex(c => c.AccountId)
|
||||||
|
.HasDatabaseName(
|
||||||
|
"ix_" +
|
||||||
|
$"{builder.Metadata.GetTableName()}_" +
|
||||||
|
$"{builder.Property(c => c.AccountId).Metadata.GetColumnName()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user