Merge pull request 'last changes merge' (#2) from development into main
Reviewed-on: #2
This commit is contained in:
commit
89420ce1ee
1
.github/workflows/ci-cd.yml
vendored
1
.github/workflows/ci-cd.yml
vendored
@ -25,7 +25,6 @@ jobs:
|
|||||||
cache: true
|
cache: true
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
src/Application/packages.lock.json
|
src/Application/packages.lock.json
|
||||||
src/Identity/packages.lock.json
|
|
||||||
src/Infrastructure/packages.lock.json
|
src/Infrastructure/packages.lock.json
|
||||||
src/Persistence/packages.lock.json
|
src/Persistence/packages.lock.json
|
||||||
src/Configuration/packages.lock.json
|
src/Configuration/packages.lock.json
|
||||||
|
@ -13,8 +13,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Infra
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpApi", "src\HttpApi\HttpApi.csproj", "{4431B3CB-A5F2-447A-8BC7-9DC3DA9E6A6D}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpApi", "src\HttpApi\HttpApi.csproj", "{4431B3CB-A5F2-447A-8BC7-9DC3DA9E6A6D}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity", "src\Identity\Identity.csproj", "{AD3CC01F-0331-44DC-B58E-CCE6ADCB56B6}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Configuration", "src\Configuration\Configuration.csproj", "{1DCFA4EE-A545-42FE-A3BC-A606D2961298}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Configuration", "src\Configuration\Configuration.csproj", "{1DCFA4EE-A545-42FE-A3BC-A606D2961298}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application.IntegrationTests", "tst\Application.IntegrationTests\Application.IntegrationTests.csproj", "{B52B8651-10B8-488D-8ACF-9C4499F8A723}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application.IntegrationTests", "tst\Application.IntegrationTests\Application.IntegrationTests.csproj", "{B52B8651-10B8-488D-8ACF-9C4499F8A723}"
|
||||||
@ -48,10 +46,6 @@ Global
|
|||||||
{4431B3CB-A5F2-447A-8BC7-9DC3DA9E6A6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{4431B3CB-A5F2-447A-8BC7-9DC3DA9E6A6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{4431B3CB-A5F2-447A-8BC7-9DC3DA9E6A6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{4431B3CB-A5F2-447A-8BC7-9DC3DA9E6A6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{4431B3CB-A5F2-447A-8BC7-9DC3DA9E6A6D}.Release|Any CPU.Build.0 = Release|Any CPU
|
{4431B3CB-A5F2-447A-8BC7-9DC3DA9E6A6D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{AD3CC01F-0331-44DC-B58E-CCE6ADCB56B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{AD3CC01F-0331-44DC-B58E-CCE6ADCB56B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{AD3CC01F-0331-44DC-B58E-CCE6ADCB56B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{AD3CC01F-0331-44DC-B58E-CCE6ADCB56B6}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{1DCFA4EE-A545-42FE-A3BC-A606D2961298}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{1DCFA4EE-A545-42FE-A3BC-A606D2961298}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{1DCFA4EE-A545-42FE-A3BC-A606D2961298}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{1DCFA4EE-A545-42FE-A3BC-A606D2961298}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{1DCFA4EE-A545-42FE-A3BC-A606D2961298}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{1DCFA4EE-A545-42FE-A3BC-A606D2961298}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
58
src/Application/Addresses/AddressDto.cs
Normal file
58
src/Application/Addresses/AddressDto.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses;
|
||||||
|
|
||||||
|
public sealed class AddressDto : IMapFrom<Address>
|
||||||
|
{
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public double Longitude { get; set; }
|
||||||
|
|
||||||
|
public double Latitude { get; set; }
|
||||||
|
|
||||||
|
public string VehicleType { 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, AddressDto>()
|
||||||
|
.ForMember(
|
||||||
|
d => d.Uuid,
|
||||||
|
opt => opt.MapFrom(s => s.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.VehicleType,
|
||||||
|
opt => opt.MapFrom(s => s.VehicleType.Name))
|
||||||
|
.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));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Commands.AddAddress;
|
||||||
|
|
||||||
|
public record AddAddressCommand : IRequest<AddressDto>
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public double Longitude { get; set; }
|
||||||
|
|
||||||
|
public double Latitude { get; set; }
|
||||||
|
|
||||||
|
public VehicleType VehicleType { get; set; }
|
||||||
|
|
||||||
|
public Guid CityGuid { 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.Addresses.Commands.AddAddress;
|
||||||
|
|
||||||
|
public class AddAddressCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<AddAddressCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public AddAddressCommandAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(AddAddressCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInAnyOfRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles =
|
||||||
|
[IdentityRole.Administrator, IdentityRole.CompanyOwner],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Commands.AddAddress;
|
||||||
|
|
||||||
|
public class AddAddressCommandHandler :
|
||||||
|
IRequestHandler<AddAddressCommand, AddressDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public AddAddressCommandHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AddressDto> Handle(
|
||||||
|
AddAddressCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.AddressRepository.GetOneAsync(
|
||||||
|
e => e.Name == request.Name && e.City.Guid == request.CityGuid,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (entity != null)
|
||||||
|
{
|
||||||
|
throw new DuplicateEntityException(
|
||||||
|
"Address with given name already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentEntity = await _unitOfWork.CityRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.CityGuid, e => e.Region.Country,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (parentEntity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException(
|
||||||
|
$"Parent entity with Guid: {request.CityGuid} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
entity = new Address()
|
||||||
|
{
|
||||||
|
Name = request.Name,
|
||||||
|
Longitude = request.Longitude,
|
||||||
|
Latitude = request.Latitude,
|
||||||
|
VehicleType = request.VehicleType,
|
||||||
|
CityId = parentEntity.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
entity = await _unitOfWork.AddressRepository.AddOneAsync(
|
||||||
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return _mapper.Map<AddressDto>(entity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Commands.AddAddress;
|
||||||
|
|
||||||
|
public class AddAddressCommandValidator : AbstractValidator<AddAddressCommand>
|
||||||
|
{
|
||||||
|
public AddAddressCommandValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
SessionCultureService cultureService)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Name)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
|
|
||||||
|
RuleFor(v => v.Latitude)
|
||||||
|
.GreaterThanOrEqualTo(-90)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||||
|
-90))
|
||||||
|
.LessThanOrEqualTo(90)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.LessThanOrEqualTo"],
|
||||||
|
90));
|
||||||
|
|
||||||
|
RuleFor(v => v.Longitude)
|
||||||
|
.GreaterThanOrEqualTo(-180)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||||
|
-180))
|
||||||
|
.LessThanOrEqualTo(180)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.LessThanOrEqualTo"],
|
||||||
|
180));
|
||||||
|
|
||||||
|
RuleFor(v => v.VehicleType)
|
||||||
|
.Must((v, vt) => VehicleType.Enumerations.ContainsValue(vt))
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
localizer["FluentValidation.MustBeInEnum"],
|
||||||
|
String.Join(
|
||||||
|
", ",
|
||||||
|
VehicleType.Enumerations.Values.Select(e => e.Name))));
|
||||||
|
|
||||||
|
RuleFor(v => v.CityGuid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Commands.DeleteAddress;
|
||||||
|
|
||||||
|
public record DeleteAddressCommand : IRequest
|
||||||
|
{
|
||||||
|
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.Addresses.Commands.DeleteAddress;
|
||||||
|
|
||||||
|
public class DeleteAddressCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<DeleteAddressCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public DeleteAddressCommandAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(DeleteAddressCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInAnyOfRolesRequirement
|
||||||
|
{
|
||||||
|
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.Addresses.Commands.DeleteAddress;
|
||||||
|
|
||||||
|
public class DeleteAddressCommandHandler : IRequestHandler<DeleteAddressCommand>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public DeleteAddressCommandHandler(UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(
|
||||||
|
DeleteAddressCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.AddressRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, cancellationToken);
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _unitOfWork.AddressRepository.DeleteOneAsync(
|
||||||
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Commands.DeleteAddress;
|
||||||
|
|
||||||
|
public class DeleteAddressCommandValidator : AbstractValidator<DeleteAddressCommand>
|
||||||
|
{
|
||||||
|
public DeleteAddressCommandValidator(IStringLocalizer localizer)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Commands.UpdateAddress;
|
||||||
|
|
||||||
|
public record UpdateAddressCommand : IRequest<AddressDto>
|
||||||
|
{
|
||||||
|
public Guid Guid { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public double Longitude { get; set; }
|
||||||
|
|
||||||
|
public double Latitude { get; set; }
|
||||||
|
|
||||||
|
public VehicleType VehicleType { get; set; }
|
||||||
|
|
||||||
|
public Guid CityGuid { 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.Addresses.Commands.UpdateAddress;
|
||||||
|
|
||||||
|
public class UpdateAddressCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<UpdateAddressCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public UpdateAddressCommandAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(UpdateAddressCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInAnyOfRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles = [IdentityRole.Administrator],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Commands.UpdateAddress;
|
||||||
|
|
||||||
|
public class UpdateAddressCommandHandler :
|
||||||
|
IRequestHandler<UpdateAddressCommand, AddressDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public UpdateAddressCommandHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AddressDto> Handle(
|
||||||
|
UpdateAddressCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.AddressRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, e => e.City.Region.Country,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentEntity = await _unitOfWork.CityRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.CityGuid, cancellationToken);
|
||||||
|
|
||||||
|
if (parentEntity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException(
|
||||||
|
$"Parent entity with Guid: {request.CityGuid} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.Name = request.Name;
|
||||||
|
entity.Longitude = request.Longitude;
|
||||||
|
entity.Latitude = request.Latitude;
|
||||||
|
entity.VehicleType = request.VehicleType;
|
||||||
|
entity.CityId = parentEntity.Id;
|
||||||
|
|
||||||
|
entity = await _unitOfWork.AddressRepository.UpdateOneAsync(
|
||||||
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return _mapper.Map<AddressDto>(entity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Commands.UpdateAddress;
|
||||||
|
|
||||||
|
public class UpdateAddressCommandValidator : AbstractValidator<UpdateAddressCommand>
|
||||||
|
{
|
||||||
|
public UpdateAddressCommandValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
SessionCultureService cultureService)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.Name)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
|
|
||||||
|
RuleFor(v => v.CityGuid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Queries.GetAddress;
|
||||||
|
|
||||||
|
public record GetAddressQuery : IRequest<AddressDto>
|
||||||
|
{
|
||||||
|
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.Addresses.Queries.GetAddress;
|
||||||
|
|
||||||
|
public class GetAddressQueryAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<GetAddressQuery>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public GetAddressQueryAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(GetAddressQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInAnyOfRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles =
|
||||||
|
[IdentityRole.Administrator, IdentityRole.CompanyOwner],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using AutoMapper;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Queries.GetAddress;
|
||||||
|
|
||||||
|
public class GetAddressQueryHandler :
|
||||||
|
IRequestHandler<GetAddressQuery, AddressDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public GetAddressQueryHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AddressDto> Handle(
|
||||||
|
GetAddressQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.AddressRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, e => e.City.Region.Country,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _mapper.Map<AddressDto>(entity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Queries.GetAddress;
|
||||||
|
|
||||||
|
public class GetAddressQueryValidator : AbstractValidator<GetAddressQuery>
|
||||||
|
{
|
||||||
|
public GetAddressQueryValidator(IStringLocalizer localizer)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Queries.GetAddressesPage;
|
||||||
|
|
||||||
|
public record GetAddressesPageQuery : IRequest<PaginatedList<AddressDto>>
|
||||||
|
{
|
||||||
|
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 Guid? CountryGuid { get; set; }
|
||||||
|
|
||||||
|
public Guid? RegionGuid { get; set; }
|
||||||
|
|
||||||
|
public Guid? CityGuid { get; set; }
|
||||||
|
|
||||||
|
public double? LongitudeGreaterOrEqualThan { get; set; }
|
||||||
|
|
||||||
|
public double? LongitudeLessOrEqualThan { get; set; }
|
||||||
|
|
||||||
|
public double? LatitudeGreaterOrEqualThan { get; set; }
|
||||||
|
|
||||||
|
public double? LatitudeLessOrEqualThan { get; set; }
|
||||||
|
|
||||||
|
public VehicleType? VehicleType { 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.Addresses.Queries.GetAddressesPage;
|
||||||
|
|
||||||
|
public class GetAddressesPageQueryAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<GetAddressesPageQuery>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public GetAddressesPageQueryAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(GetAddressesPageQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInAnyOfRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles =
|
||||||
|
[IdentityRole.Administrator, IdentityRole.CompanyOwner],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Extensions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Queries.GetAddressesPage;
|
||||||
|
|
||||||
|
public class GetAddressesPageQueryHandler :
|
||||||
|
IRequestHandler<GetAddressesPageQuery, PaginatedList<AddressDto>>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public GetAddressesPageQueryHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PaginatedList<AddressDto>> Handle(
|
||||||
|
GetAddressesPageQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var paginatedList = await _unitOfWork.AddressRepository.GetPageAsync(
|
||||||
|
e =>
|
||||||
|
(e.Name.ToLower().Contains(request.Search.ToLower()) ||
|
||||||
|
e.City.Name.ToLower().Contains(request.Search.ToLower()) ||
|
||||||
|
e.City.Region.Name.ToLower().Contains(request.Search.ToLower()) ||
|
||||||
|
e.City.Region.Country.Name.ToLower().Contains(request.Search.ToLower())) &&
|
||||||
|
(request.LongitudeGreaterOrEqualThan != null
|
||||||
|
? e.Longitude >= request.LongitudeGreaterOrEqualThan
|
||||||
|
: true) &&
|
||||||
|
(request.LongitudeLessOrEqualThan != null
|
||||||
|
? e.Longitude <= request.LongitudeLessOrEqualThan
|
||||||
|
: true) &&
|
||||||
|
(request.LatitudeGreaterOrEqualThan != null
|
||||||
|
? e.Latitude >= request.LatitudeGreaterOrEqualThan
|
||||||
|
: true) &&
|
||||||
|
(request.LatitudeLessOrEqualThan != null
|
||||||
|
? e.Latitude <= request.LatitudeLessOrEqualThan
|
||||||
|
: true) &&
|
||||||
|
(request.VehicleType != null
|
||||||
|
? e.VehicleType == request.VehicleType
|
||||||
|
: true) &&
|
||||||
|
(request.CityGuid != null
|
||||||
|
? e.City.Guid == request.CityGuid
|
||||||
|
: true) &&
|
||||||
|
(request.RegionGuid != null
|
||||||
|
? e.City.Region.Guid == request.RegionGuid
|
||||||
|
: true) &&
|
||||||
|
(request.CountryGuid != null
|
||||||
|
? e.City.Region.Country.Guid == request.CountryGuid
|
||||||
|
: true),
|
||||||
|
e => e.City.Region.Country,
|
||||||
|
request.PageNumber, request.PageSize,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
var mappedItems = _mapper
|
||||||
|
.ProjectTo<AddressDto>(paginatedList.Items.AsQueryable());
|
||||||
|
|
||||||
|
mappedItems = QueryableExtension<AddressDto>
|
||||||
|
.ApplySort(mappedItems, request.Sort);
|
||||||
|
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return new PaginatedList<AddressDto>(
|
||||||
|
mappedItems.ToList(),
|
||||||
|
paginatedList.TotalCount, request.PageNumber,
|
||||||
|
request.PageSize);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.Queries.GetAddressesPage;
|
||||||
|
|
||||||
|
public class GetAddressesPageQueryValidator : AbstractValidator<GetAddressesPageQuery>
|
||||||
|
{
|
||||||
|
public GetAddressesPageQueryValidator(
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
14
src/Application/Addresses/ViewModels/AddAddressViewModel.cs
Normal file
14
src/Application/Addresses/ViewModels/AddAddressViewModel.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.ViewModels;
|
||||||
|
|
||||||
|
public sealed class AddAddressViewModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public double Longitude { get; set; }
|
||||||
|
|
||||||
|
public double Latitude { get; set; }
|
||||||
|
|
||||||
|
public string VehicleType { get; set; }
|
||||||
|
|
||||||
|
public Guid CityUuid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.ViewModels;
|
||||||
|
|
||||||
|
public sealed class GetAddressesPageFilterViewModel
|
||||||
|
{
|
||||||
|
public Guid? CountryUuid { get; set; }
|
||||||
|
|
||||||
|
public Guid? RegionUuid { get; set; }
|
||||||
|
|
||||||
|
public Guid? CityUuid { get; set; }
|
||||||
|
|
||||||
|
public double? LongitudeGreaterOrEqualThan { get; set; }
|
||||||
|
|
||||||
|
public double? LongitudeLessOrEqualThan { get; set; }
|
||||||
|
|
||||||
|
public double? LatitudeGreaterOrEqualThan { get; set; }
|
||||||
|
|
||||||
|
public double? LatitudeLessOrEqualThan { get; set; }
|
||||||
|
|
||||||
|
public string? VehicleType { get; set; }
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Addresses.ViewModels;
|
||||||
|
|
||||||
|
public sealed class UpdateAddressViewModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public double Longitude { get; set; }
|
||||||
|
|
||||||
|
public double Latitude { get; set; }
|
||||||
|
|
||||||
|
public string VehicleType { get; set; }
|
||||||
|
|
||||||
|
public Guid CityUuid { get; set; }
|
||||||
|
}
|
28
src/Application/Aircrafts/AircraftDto.cs
Normal file
28
src/Application/Aircrafts/AircraftDto.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts;
|
||||||
|
|
||||||
|
public sealed class AircraftDto : IMapFrom<Aircraft>
|
||||||
|
{
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public string Number { get; set; }
|
||||||
|
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public short Capacity { get; set; }
|
||||||
|
|
||||||
|
public Guid CompanyUuid { get; set; }
|
||||||
|
|
||||||
|
public void Mapping(MappingProfile profile)
|
||||||
|
{
|
||||||
|
profile.CreateMap<Aircraft, AircraftDto>()
|
||||||
|
.ForMember(
|
||||||
|
d => d.Uuid,
|
||||||
|
opt => opt.MapFrom(s => s.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.CompanyUuid,
|
||||||
|
opt => opt.MapFrom(s => s.Company.Guid));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.AddAircraft;
|
||||||
|
|
||||||
|
public record AddAircraftCommand : IRequest<AircraftDto>
|
||||||
|
{
|
||||||
|
public string Number { get; set; }
|
||||||
|
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public short Capacity { get; set; }
|
||||||
|
|
||||||
|
public Guid CompanyGuid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.AddAircraft;
|
||||||
|
|
||||||
|
public class AddAircraftCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<AddAircraftCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public AddAircraftCommandAuthorizer(
|
||||||
|
SessionUserService sessionUserService,
|
||||||
|
UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(AddAircraftCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
var company = _unitOfWork.CompanyRepository
|
||||||
|
.GetOneAsync(
|
||||||
|
e => e.Guid == request.CompanyGuid, e => e.Account,
|
||||||
|
CancellationToken.None)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
UseRequirement(new MustBeObjectOwnerOrAdminRequirement
|
||||||
|
{
|
||||||
|
UserRoles = _sessionUserService.Roles,
|
||||||
|
RequiredGuid = company?.Account.Guid,
|
||||||
|
UserGuid = _sessionUserService.Guid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.AddAircraft;
|
||||||
|
|
||||||
|
public class AddAircraftCommandHandler :
|
||||||
|
IRequestHandler<AddAircraftCommand, AircraftDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public AddAircraftCommandHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AircraftDto> Handle(
|
||||||
|
AddAircraftCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.AircraftRepository.GetOneAsync(
|
||||||
|
e => e.Number == request.Number, cancellationToken);
|
||||||
|
|
||||||
|
if (entity != null)
|
||||||
|
{
|
||||||
|
throw new DuplicateEntityException(
|
||||||
|
"Aircraft with given number already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentEntity = await _unitOfWork.CompanyRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.CompanyGuid, cancellationToken);
|
||||||
|
|
||||||
|
if (parentEntity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException(
|
||||||
|
$"Parent entity with Guid: {request.CompanyGuid} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
entity = new Aircraft()
|
||||||
|
{
|
||||||
|
Number = request.Number,
|
||||||
|
Model = request.Model,
|
||||||
|
Capacity = request.Capacity,
|
||||||
|
CompanyId = parentEntity.Id,
|
||||||
|
Company = parentEntity
|
||||||
|
};
|
||||||
|
|
||||||
|
entity = await _unitOfWork.AircraftRepository.AddOneAsync(
|
||||||
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return _mapper.Map<AircraftDto>(entity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.AddAircraft;
|
||||||
|
|
||||||
|
public class AddAircraftCommandValidator : AbstractValidator<AddAircraftCommand>
|
||||||
|
{
|
||||||
|
public AddAircraftCommandValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
SessionCultureService cultureService)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Number)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MaximumLength(32)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
32));
|
||||||
|
|
||||||
|
RuleFor(v => v.Model)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
|
|
||||||
|
RuleFor(v => v.Capacity)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.CompanyGuid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.DeleteAircraft;
|
||||||
|
|
||||||
|
public record DeleteAircraftCommand : IRequest
|
||||||
|
{
|
||||||
|
public Guid Guid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.DeleteAircraft;
|
||||||
|
|
||||||
|
public class DeleteAircraftCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<DeleteAircraftCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public DeleteAircraftCommandAuthorizer(
|
||||||
|
SessionUserService sessionUserService,
|
||||||
|
UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(DeleteAircraftCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
var vehicel = _unitOfWork.VehicleRepository
|
||||||
|
.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, e => e.Company.Account,
|
||||||
|
CancellationToken.None)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
UseRequirement(new MustBeObjectOwnerOrAdminRequirement
|
||||||
|
{
|
||||||
|
UserRoles = _sessionUserService.Roles,
|
||||||
|
RequiredGuid = vehicel?.Company.Account.Guid,
|
||||||
|
UserGuid = _sessionUserService.Guid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.DeleteAircraft;
|
||||||
|
|
||||||
|
public class DeleteAircraftCommandHandler : IRequestHandler<DeleteAircraftCommand>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public DeleteAircraftCommandHandler(UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(
|
||||||
|
DeleteAircraftCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.AircraftRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, cancellationToken);
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _unitOfWork.AircraftRepository.DeleteOneAsync(
|
||||||
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.DeleteAircraft;
|
||||||
|
|
||||||
|
public class DeleteAircraftCommandValidator : AbstractValidator<DeleteAircraftCommand>
|
||||||
|
{
|
||||||
|
public DeleteAircraftCommandValidator(IStringLocalizer localizer)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.UpdateAircraft;
|
||||||
|
|
||||||
|
public record UpdateAircraftCommand : IRequest<AircraftDto>
|
||||||
|
{
|
||||||
|
public Guid Guid { get; set; }
|
||||||
|
|
||||||
|
public string Number { get; set; }
|
||||||
|
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public short Capacity { get; set; }
|
||||||
|
|
||||||
|
public Guid CompanyGuid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.UpdateAircraft;
|
||||||
|
|
||||||
|
public class UpdateAircraftCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<UpdateAircraftCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public UpdateAircraftCommandAuthorizer(
|
||||||
|
SessionUserService sessionUserService,
|
||||||
|
UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(UpdateAircraftCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
var company = _unitOfWork.CompanyRepository
|
||||||
|
.GetOneAsync(
|
||||||
|
e => e.Guid == request.CompanyGuid, e => e.Account,
|
||||||
|
CancellationToken.None)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
UseRequirement(new MustBeObjectOwnerOrAdminRequirement
|
||||||
|
{
|
||||||
|
UserRoles = _sessionUserService.Roles,
|
||||||
|
RequiredGuid = company?.Account.Guid,
|
||||||
|
UserGuid = _sessionUserService.Guid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.UpdateAircraft;
|
||||||
|
|
||||||
|
public class UpdateAircraftCommandHandler :
|
||||||
|
IRequestHandler<UpdateAircraftCommand, AircraftDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public UpdateAircraftCommandHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AircraftDto> Handle(
|
||||||
|
UpdateAircraftCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.AircraftRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, cancellationToken);
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var duplicateEntity = await _unitOfWork.AircraftRepository.GetOneAsync(
|
||||||
|
e => e.Number == request.Number && e.Guid != request.Guid,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (duplicateEntity != null)
|
||||||
|
{
|
||||||
|
throw new DuplicateEntityException(
|
||||||
|
"Aircraft with given number already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentEntity = await _unitOfWork.CompanyRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.CompanyGuid, cancellationToken);
|
||||||
|
|
||||||
|
if (parentEntity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException(
|
||||||
|
$"Parent entity with Guid: {request.CompanyGuid} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.Number = request.Number;
|
||||||
|
entity.Model = request.Model;
|
||||||
|
entity.Capacity = request.Capacity;
|
||||||
|
entity.CompanyId = parentEntity.Id;
|
||||||
|
entity.Company = parentEntity;
|
||||||
|
|
||||||
|
entity = await _unitOfWork.AircraftRepository.UpdateOneAsync(
|
||||||
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return _mapper.Map<AircraftDto>(entity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Commands.UpdateAircraft;
|
||||||
|
|
||||||
|
public class UpdateAircraftCommandValidator : AbstractValidator<UpdateAircraftCommand>
|
||||||
|
{
|
||||||
|
public UpdateAircraftCommandValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
SessionCultureService cultureService)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.Number)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MaximumLength(32)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
32));
|
||||||
|
|
||||||
|
RuleFor(v => v.Model)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
|
|
||||||
|
RuleFor(v => v.Capacity)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.CompanyGuid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Queries.GetAircraft;
|
||||||
|
|
||||||
|
public record GetAircraftQuery : IRequest<AircraftDto>
|
||||||
|
{
|
||||||
|
public Guid Guid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Queries.GetAircraft;
|
||||||
|
|
||||||
|
public class GetAircraftQueryAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<GetAircraftQuery>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public GetAircraftQueryAuthorizer(
|
||||||
|
SessionUserService sessionUserService,
|
||||||
|
UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(GetAircraftQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
var vehicel = _unitOfWork.VehicleRepository
|
||||||
|
.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, e => e.Company.Account,
|
||||||
|
CancellationToken.None)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
UseRequirement(new MustBeObjectOwnerOrAdminRequirement
|
||||||
|
{
|
||||||
|
UserRoles = _sessionUserService.Roles,
|
||||||
|
RequiredGuid = vehicel?.Company.Account.Guid,
|
||||||
|
UserGuid = _sessionUserService.Guid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using AutoMapper;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Queries.GetAircraft;
|
||||||
|
|
||||||
|
public class GetAircraftQueryHandler :
|
||||||
|
IRequestHandler<GetAircraftQuery, AircraftDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public GetAircraftQueryHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AircraftDto> Handle(
|
||||||
|
GetAircraftQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.AircraftRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, e => e.Company,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _mapper.Map<AircraftDto>(entity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Queries.GetAircraft;
|
||||||
|
|
||||||
|
public class GetAircraftQueryValidator : AbstractValidator<GetAircraftQuery>
|
||||||
|
{
|
||||||
|
public GetAircraftQueryValidator(IStringLocalizer localizer)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Queries.GetAircraftsPage;
|
||||||
|
|
||||||
|
public record GetAircraftsPageQuery : IRequest<PaginatedList<AircraftDto>>
|
||||||
|
{
|
||||||
|
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 Guid? CompanyGuid { get; set; }
|
||||||
|
|
||||||
|
public short? CapacityGreaterThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public short? CapacityLessThanOrEqualTo { get; set; }
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Queries.GetAircraftsPage;
|
||||||
|
|
||||||
|
public class GetAircraftsPageQueryAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<GetAircraftsPageQuery>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public GetAircraftsPageQueryAuthorizer(
|
||||||
|
SessionUserService sessionUserService,
|
||||||
|
UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(GetAircraftsPageQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
var company = _unitOfWork.CompanyRepository
|
||||||
|
.GetOneAsync(
|
||||||
|
e => e.Guid == request.CompanyGuid, e => e.Account,
|
||||||
|
CancellationToken.None)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
UseRequirement(new MustBeObjectOwnerOrAdminRequirement
|
||||||
|
{
|
||||||
|
UserRoles = _sessionUserService.Roles,
|
||||||
|
RequiredGuid = company?.Account.Guid,
|
||||||
|
UserGuid = _sessionUserService.Guid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Extensions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Queries.GetAircraftsPage;
|
||||||
|
|
||||||
|
public class GetAircraftsPageQueryHandler :
|
||||||
|
IRequestHandler<GetAircraftsPageQuery, PaginatedList<AircraftDto>>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public GetAircraftsPageQueryHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PaginatedList<AircraftDto>> Handle(
|
||||||
|
GetAircraftsPageQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var paginatedList = await _unitOfWork.AircraftRepository.GetPageAsync(
|
||||||
|
e =>
|
||||||
|
(e.Number.ToLower().Contains(request.Search.ToLower()) ||
|
||||||
|
e.Model.ToLower().Contains(request.Search.ToLower())) &&
|
||||||
|
(request.CompanyGuid != null
|
||||||
|
? e.Company.Guid == request.CompanyGuid
|
||||||
|
: true) &&
|
||||||
|
(request.CapacityGreaterThanOrEqualTo != null
|
||||||
|
? e.Capacity >= request.CapacityGreaterThanOrEqualTo
|
||||||
|
: true) &&
|
||||||
|
(request.CapacityLessThanOrEqualTo != null
|
||||||
|
? e.Capacity <= request.CapacityLessThanOrEqualTo
|
||||||
|
: true),
|
||||||
|
e => e.Company,
|
||||||
|
request.PageNumber, request.PageSize,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
var mappedItems = _mapper
|
||||||
|
.ProjectTo<AircraftDto>(paginatedList.Items.AsQueryable());
|
||||||
|
|
||||||
|
mappedItems = QueryableExtension<AircraftDto>
|
||||||
|
.ApplySort(mappedItems, request.Sort);
|
||||||
|
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return new PaginatedList<AircraftDto>(
|
||||||
|
mappedItems.ToList(),
|
||||||
|
paginatedList.TotalCount, request.PageNumber,
|
||||||
|
request.PageSize);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.Queries.GetAircraftsPage;
|
||||||
|
|
||||||
|
public class GetAircraftsPageQueryValidator : AbstractValidator<GetAircraftsPageQuery>
|
||||||
|
{
|
||||||
|
public GetAircraftsPageQueryValidator(
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
12
src/Application/Aircrafts/ViewModels/AddAircraftViewModel.cs
Normal file
12
src/Application/Aircrafts/ViewModels/AddAircraftViewModel.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.ViewModels;
|
||||||
|
|
||||||
|
public sealed class AddAircraftViewModel
|
||||||
|
{
|
||||||
|
public string Number { get; set; }
|
||||||
|
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public short Capacity { get; set; }
|
||||||
|
|
||||||
|
public Guid CompanyUuid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.ViewModels;
|
||||||
|
|
||||||
|
public sealed class GetAircraftsPageFilterViewModel
|
||||||
|
{
|
||||||
|
public Guid? CompanyUuid { get; set; }
|
||||||
|
|
||||||
|
// TODO: Consider adding strict equals rule although it is not
|
||||||
|
// necessarily needed to filter with exact capacity
|
||||||
|
|
||||||
|
public short? CapacityGreaterThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public short? CapacityLessThanOrEqualTo { get; set; }
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Aircrafts.ViewModels;
|
||||||
|
|
||||||
|
public sealed class UpdateAircraftViewModel
|
||||||
|
{
|
||||||
|
public string Number { get; set; }
|
||||||
|
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public short Capacity { get; set; }
|
||||||
|
|
||||||
|
public Guid CompanyUuid { get; set; }
|
||||||
|
}
|
@ -16,7 +16,12 @@
|
|||||||
<PackageReference Include="FluentValidation" Version="11.11.0" />
|
<PackageReference Include="FluentValidation" Version="11.11.0" />
|
||||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||||
<PackageReference Include="MediatR.Behaviors.Authorization" Version="12.2.0" />
|
<PackageReference Include="MediatR.Behaviors.Authorization" Version="12.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.11.0" />
|
||||||
|
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.11.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="QuikGraph" Version="2.5.0" />
|
||||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.2" />
|
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@ namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.Register;
|
|||||||
|
|
||||||
public record RegisterCommand : IRequest
|
public record RegisterCommand : IRequest
|
||||||
{
|
{
|
||||||
|
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,13 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.Register;
|
||||||
|
|
||||||
|
public class RegisterCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<RegisterCommand>
|
||||||
|
{
|
||||||
|
public override void BuildPolicy(RegisterCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new AllowAllRequirement());
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,83 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Enums;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.Register;
|
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.Register;
|
||||||
|
|
||||||
public class RegisterCommandHandler : IRequestHandler<RegisterCommand>
|
public class RegisterCommandHandler : IRequestHandler<RegisterCommand>
|
||||||
{
|
{
|
||||||
private readonly AuthenticationService _authenticationService;
|
private readonly IReadOnlyCollection<IdentityRole> DefaultRoles =
|
||||||
|
new IdentityRole[] { IdentityRole.User };
|
||||||
|
|
||||||
public RegisterCommandHandler(AuthenticationService authenticationService)
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly PasswordHasherService _passwordHasher;
|
||||||
|
|
||||||
|
public RegisterCommandHandler(UnitOfWork unitOfWork,
|
||||||
|
PasswordHasherService passwordHasher)
|
||||||
{
|
{
|
||||||
_authenticationService = authenticationService;
|
_unitOfWork = unitOfWork;
|
||||||
|
_passwordHasher = passwordHasher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(
|
public async Task Handle(RegisterCommand request,
|
||||||
RegisterCommand request, CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _authenticationService.RegisterAsync(
|
var datastoreAccount = await _unitOfWork.AccountRepository
|
||||||
request.Email, request.Password, cancellationToken);
|
.GetOneAsync(
|
||||||
|
e =>
|
||||||
|
e.Email == request.Email ||
|
||||||
|
e.Username == request.Username,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (datastoreAccount != null)
|
||||||
|
{
|
||||||
|
throw new RegistrationException(
|
||||||
|
"User with given email or username already registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var defaultRoleIds = (await _unitOfWork.RoleRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
r => DefaultRoles.Contains(r.Value),
|
||||||
|
1, DefaultRoles.Count, cancellationToken))
|
||||||
|
.Items
|
||||||
|
.Select(r => r.Id);
|
||||||
|
|
||||||
|
|
||||||
|
var password = Encoding.UTF8.GetBytes(request.Password);
|
||||||
|
|
||||||
|
var salt = RandomNumberGenerator.GetBytes(128 / 8);
|
||||||
|
var hash = await _passwordHasher
|
||||||
|
.HashAsync(password, salt, cancellationToken);
|
||||||
|
|
||||||
|
var saltBase64 = Convert.ToBase64String(salt);
|
||||||
|
var hashBase64 = Convert.ToBase64String(hash);
|
||||||
|
|
||||||
|
|
||||||
|
var newAccount = new Account
|
||||||
|
{
|
||||||
|
Username = request.Username,
|
||||||
|
Email = request.Email,
|
||||||
|
PasswordHash = hashBase64,
|
||||||
|
PasswordSalt = saltBase64,
|
||||||
|
AccountRoles = defaultRoleIds.Select(id =>
|
||||||
|
new AccountRole()
|
||||||
|
{
|
||||||
|
RoleId = id
|
||||||
|
})
|
||||||
|
.ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
await _unitOfWork.AccountRepository
|
||||||
|
.AddOneAsync(newAccount, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,54 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.FluentValidation;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.Register;
|
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.Register;
|
||||||
|
|
||||||
public class RegisterCommandValidator : AbstractValidator<RegisterCommand>
|
public class RegisterCommandValidator : AbstractValidator<RegisterCommand>
|
||||||
{
|
{
|
||||||
public RegisterCommandValidator()
|
public RegisterCommandValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
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("Email address is required.")
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
.Matches(@"\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b")
|
.IsEmail()
|
||||||
.WithMessage("Email address is invalid.");
|
.WithMessage(localizer["FluentValidation.IsEmail"]);
|
||||||
|
|
||||||
RuleFor(v => v.Password)
|
RuleFor(v => v.Password)
|
||||||
.NotEmpty()
|
.NotEmpty()
|
||||||
.WithMessage("Password is required.")
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
.MinimumLength(8)
|
.MinimumLength(8)
|
||||||
.WithMessage("Password must be at least 8 characters long.")
|
.WithMessage(
|
||||||
.MaximumLength(64)
|
String.Format(
|
||||||
.WithMessage("Password must be at most 64 characters long.")
|
cultureService.Culture,
|
||||||
.Matches(@"(?=.*[A-Z]).*")
|
localizer["FluentValidation.MinimumLength"],
|
||||||
.WithMessage("Password must contain at least one uppercase letter.")
|
8))
|
||||||
.Matches(@"(?=.*[a-z]).*")
|
.MaximumLength(256)
|
||||||
.WithMessage("Password must contain at least one lowercase letter.")
|
.WithMessage(
|
||||||
.Matches(@"(?=.*[\d]).*")
|
String.Format(
|
||||||
.WithMessage("Password must contain at least one digit.")
|
cultureService.Culture,
|
||||||
.Matches(@"(?=.*[!@#$%^&*()]).*")
|
localizer["FluentValidation.MaximumLength"],
|
||||||
.WithMessage("Password must contain at least one of the following special charactters: !@#$%^&*().");
|
256));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
|
||||||
using MediatR.Behaviors.Authorization;
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RenewAccessToken;
|
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RenewAccessToken;
|
||||||
@ -7,18 +6,8 @@ namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RenewAccessToken
|
|||||||
public class RenewAccessTokenCommandAuthorizer :
|
public class RenewAccessTokenCommandAuthorizer :
|
||||||
AbstractRequestAuthorizer<RenewAccessTokenCommand>
|
AbstractRequestAuthorizer<RenewAccessTokenCommand>
|
||||||
{
|
{
|
||||||
private readonly SessionUserService _sessionUserService;
|
|
||||||
|
|
||||||
public RenewAccessTokenCommandAuthorizer(SessionUserService currentUserService)
|
|
||||||
{
|
|
||||||
_sessionUserService = currentUserService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void BuildPolicy(RenewAccessTokenCommand request)
|
public override void BuildPolicy(RenewAccessTokenCommand request)
|
||||||
{
|
{
|
||||||
UseRequirement(new MustBeAuthenticatedRequirement
|
UseRequirement(new AllowAllRequirement());
|
||||||
{
|
|
||||||
IsAuthenticated = _sessionUserService.IsAuthenticated
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,95 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RenewAccessToken;
|
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RenewAccessToken;
|
||||||
|
|
||||||
public class RenewAccessTokenCommandHandler :
|
public class RenewAccessTokenCommandHandler :
|
||||||
IRequestHandler<RenewAccessTokenCommand, TokensModel>
|
IRequestHandler<RenewAccessTokenCommand, TokensModel>
|
||||||
{
|
{
|
||||||
private readonly AuthenticationService _authenticationService;
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly JsonWebTokenConfigurationOptions _jwtConfiguration;
|
||||||
|
|
||||||
public RenewAccessTokenCommandHandler(AuthenticationService authenticationService)
|
public RenewAccessTokenCommandHandler(UnitOfWork unitOfWork,
|
||||||
|
IOptions<ConfigurationOptions> configurationOptions)
|
||||||
{
|
{
|
||||||
_authenticationService = authenticationService;
|
_unitOfWork = unitOfWork;
|
||||||
|
_jwtConfiguration = configurationOptions.Value.JsonWebToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TokensModel> Handle(
|
public async Task<TokensModel> Handle(
|
||||||
RenewAccessTokenCommand request, CancellationToken cancellationToken)
|
RenewAccessTokenCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return await _authenticationService.RenewAccessTokenAsync(
|
var refreshToken = (await _unitOfWork.RefreshTokenRepository
|
||||||
request.RefreshToken, cancellationToken);
|
.GetOneAsync(e => e.Value == request.RefreshToken,
|
||||||
|
cancellationToken));
|
||||||
|
|
||||||
|
if (refreshToken == null)
|
||||||
|
{
|
||||||
|
throw new AuthenticationException($"Refresh token was not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!refreshToken.IsActive)
|
||||||
|
{
|
||||||
|
throw new AuthenticationException("Refresh token is inactive.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var account = await _unitOfWork.AccountRepository.GetOneAsync(
|
||||||
|
a => a.RefreshTokens.Contains(refreshToken),
|
||||||
|
a => a.AccountRoles, cancellationToken);
|
||||||
|
|
||||||
|
var jwtSecurityToken = await CreateJwtAsync(account, cancellationToken);
|
||||||
|
var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
|
||||||
|
|
||||||
|
return new TokensModel(accessToken, refreshToken.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<JwtSecurityToken> CreateJwtAsync(
|
||||||
|
Account account, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var roleIds = account.AccountRoles.Select(ar => ar.RoleId);
|
||||||
|
|
||||||
|
var roles = (await _unitOfWork.RoleRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
r => roleIds.Contains(r.Id),
|
||||||
|
1, roleIds.Count(), cancellationToken))
|
||||||
|
.Items.Select(r => r.Value);
|
||||||
|
|
||||||
|
var roleClaims = new List<Claim>();
|
||||||
|
foreach (var role in roles)
|
||||||
|
{
|
||||||
|
roleClaims.Add(new Claim("roles", role.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims = new List<Claim>()
|
||||||
|
{
|
||||||
|
new Claim(JwtRegisteredClaimNames.Sub, account.Guid.ToString()),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Nickname, account.Username),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Email, account.Email)
|
||||||
|
}
|
||||||
|
.Union(roleClaims);
|
||||||
|
|
||||||
|
var expirationDateTimeUtc = DateTime.UtcNow.Add(
|
||||||
|
_jwtConfiguration.AccessTokenValidity);
|
||||||
|
|
||||||
|
var symmetricSecurityKey = new SymmetricSecurityKey(
|
||||||
|
Encoding.UTF8.GetBytes(_jwtConfiguration.IssuerSigningKey));
|
||||||
|
var signingCredentials = new SigningCredentials(
|
||||||
|
symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
|
||||||
|
|
||||||
|
var jwtSecurityToken = new JwtSecurityToken(
|
||||||
|
issuer: _jwtConfiguration.Issuer,
|
||||||
|
audience: _jwtConfiguration.Audience,
|
||||||
|
claims: claims,
|
||||||
|
expires: expirationDateTimeUtc,
|
||||||
|
signingCredentials: signingCredentials);
|
||||||
|
|
||||||
|
return jwtSecurityToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands
|
|
||||||
.RenewAccessTokenWithCookie;
|
|
||||||
|
|
||||||
public record RenewAccessTokenWithCookieCommand : IRequest<TokensModel> { }
|
|
@ -1,26 +0,0 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
|
||||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
|
||||||
using MediatR.Behaviors.Authorization;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands
|
|
||||||
.RenewAccessTokenWithCookie;
|
|
||||||
|
|
||||||
public class RenewAccessTokenWithCookieCommandAuthorizer :
|
|
||||||
AbstractRequestAuthorizer<RenewAccessTokenWithCookieCommand>
|
|
||||||
{
|
|
||||||
private readonly SessionUserService _sessionUserService;
|
|
||||||
|
|
||||||
public RenewAccessTokenWithCookieCommandAuthorizer(
|
|
||||||
SessionUserService currentUserService)
|
|
||||||
{
|
|
||||||
_sessionUserService = currentUserService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void BuildPolicy(RenewAccessTokenWithCookieCommand request)
|
|
||||||
{
|
|
||||||
UseRequirement(new MustBeAuthenticatedRequirement
|
|
||||||
{
|
|
||||||
IsAuthenticated = _sessionUserService.IsAuthenticated
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
|
||||||
using MediatR;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands
|
|
||||||
.RenewAccessTokenWithCookie;
|
|
||||||
|
|
||||||
public class RenewAccessTokenWithCookieCommandHandler :
|
|
||||||
IRequestHandler<RenewAccessTokenWithCookieCommand, TokensModel>
|
|
||||||
{
|
|
||||||
private readonly AuthenticationService _authenticationService;
|
|
||||||
private readonly SessionUserService _sessionUserService;
|
|
||||||
|
|
||||||
public RenewAccessTokenWithCookieCommandHandler(
|
|
||||||
AuthenticationService authenticationService,
|
|
||||||
SessionUserService sessionUserService)
|
|
||||||
{
|
|
||||||
_authenticationService = authenticationService;
|
|
||||||
_sessionUserService = sessionUserService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<TokensModel> Handle(
|
|
||||||
RenewAccessTokenWithCookieCommand request,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return await _authenticationService.RenewAccessTokenAsync(
|
|
||||||
_sessionUserService.RefreshToken, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands
|
|
||||||
.RenewAccessTokenWithCookie;
|
|
||||||
|
|
||||||
public class RenewAccessTokenWithCookieCommandValidator :
|
|
||||||
AbstractValidator<RenewAccessTokenWithCookieCommand>
|
|
||||||
{
|
|
||||||
public RenewAccessTokenWithCookieCommandValidator() { }
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
using MediatR.Behaviors.Authorization;
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RevokeRefreshToken;
|
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RevokeRefreshToken;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RevokeRefreshToken;
|
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RevokeRefreshToken;
|
||||||
@ -6,17 +7,36 @@ namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RevokeRefreshTok
|
|||||||
public class RevokeRefreshTokenCommandHandler :
|
public class RevokeRefreshTokenCommandHandler :
|
||||||
IRequestHandler<RevokeRefreshTokenCommand>
|
IRequestHandler<RevokeRefreshTokenCommand>
|
||||||
{
|
{
|
||||||
private readonly AuthenticationService _authenticationService;
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
public RevokeRefreshTokenCommandHandler(AuthenticationService authenticationService)
|
public RevokeRefreshTokenCommandHandler(UnitOfWork unitOfWork)
|
||||||
{
|
{
|
||||||
_authenticationService = authenticationService;
|
_unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Handle(
|
public async Task Handle(
|
||||||
RevokeRefreshTokenCommand request, CancellationToken cancellationToken)
|
RevokeRefreshTokenCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _authenticationService.RevokeRefreshTokenAsync(
|
var refreshToken = (await _unitOfWork.RefreshTokenRepository
|
||||||
request.RefreshToken, cancellationToken);
|
.GetOneAsync(e => e.Value == request.RefreshToken,
|
||||||
|
cancellationToken));
|
||||||
|
|
||||||
|
if (refreshToken == null)
|
||||||
|
{
|
||||||
|
throw new AuthenticationException("Invalid refreshToken");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!refreshToken.IsActive)
|
||||||
|
{
|
||||||
|
throw new AuthenticationException("RefreshToken already revoked");
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshToken.RevocationTime = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
await _unitOfWork.RefreshTokenRepository
|
||||||
|
.UpdateOneAsync(refreshToken, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
using MediatR;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands
|
|
||||||
.RevokeRefreshTokenWithCookie;
|
|
||||||
|
|
||||||
public record RevokeRefreshTokenWithCookieCommand : IRequest { }
|
|
@ -1,26 +0,0 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
|
||||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
|
||||||
using MediatR.Behaviors.Authorization;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands
|
|
||||||
.RevokeRefreshTokenWithCookie;
|
|
||||||
|
|
||||||
public class RevokeRefreshTokenWithCookieCommandAuthorizer :
|
|
||||||
AbstractRequestAuthorizer<RevokeRefreshTokenWithCookieCommand>
|
|
||||||
{
|
|
||||||
private readonly SessionUserService _sessionUserService;
|
|
||||||
|
|
||||||
public RevokeRefreshTokenWithCookieCommandAuthorizer(
|
|
||||||
SessionUserService currentUserService)
|
|
||||||
{
|
|
||||||
_sessionUserService = currentUserService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void BuildPolicy(RevokeRefreshTokenWithCookieCommand request)
|
|
||||||
{
|
|
||||||
UseRequirement(new MustBeAuthenticatedRequirement
|
|
||||||
{
|
|
||||||
IsAuthenticated = _sessionUserService.IsAuthenticated
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
|
||||||
using MediatR;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands
|
|
||||||
.RevokeRefreshTokenWithCookie;
|
|
||||||
|
|
||||||
public class RevokeRefreshTokenWithCookieCommandHandler :
|
|
||||||
IRequestHandler<RevokeRefreshTokenWithCookieCommand>
|
|
||||||
{
|
|
||||||
private readonly AuthenticationService _authenticationService;
|
|
||||||
private readonly SessionUserService _sessionUserService;
|
|
||||||
|
|
||||||
public RevokeRefreshTokenWithCookieCommandHandler(
|
|
||||||
AuthenticationService authenticationService,
|
|
||||||
SessionUserService sessionUserService)
|
|
||||||
{
|
|
||||||
_authenticationService = authenticationService;
|
|
||||||
_sessionUserService = sessionUserService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Handle(
|
|
||||||
RevokeRefreshTokenWithCookieCommand request,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await _authenticationService.RevokeRefreshTokenAsync(
|
|
||||||
_sessionUserService.RefreshToken, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
using FluentValidation;
|
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands
|
|
||||||
.RevokeRefreshTokenWithCookie;
|
|
||||||
|
|
||||||
public class RevokeRefreshTokenWithCookieCommandValidator :
|
|
||||||
AbstractValidator<RevokeRefreshTokenWithCookieCommand>
|
|
||||||
{
|
|
||||||
public RevokeRefreshTokenWithCookieCommandValidator() { }
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ namespace cuqmbr.TravelGuide.Application.Authenticaion.Queries.Login;
|
|||||||
|
|
||||||
public record LoginQuery : IRequest<TokensModel>
|
public record LoginQuery : IRequest<TokensModel>
|
||||||
{
|
{
|
||||||
public string Email { get; set; }
|
public string EmailOrUsername { get; set; }
|
||||||
|
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Authenticaion.Queries.Login;
|
||||||
|
|
||||||
|
public class LoginQueryAuthorizer : AbstractRequestAuthorizer<LoginQuery>
|
||||||
|
{
|
||||||
|
public override void BuildPolicy(LoginQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new AllowAllRequirement());
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,140 @@
|
|||||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Queries.Login;
|
namespace cuqmbr.TravelGuide.Application.Authenticaion.Queries.Login;
|
||||||
|
|
||||||
public class LoginQueryHandler : IRequestHandler<LoginQuery, TokensModel>
|
public class LoginQueryHandler : IRequestHandler<LoginQuery, TokensModel>
|
||||||
{
|
{
|
||||||
private readonly AuthenticationService _authenticationService;
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly PasswordHasherService _passwordHasher;
|
||||||
|
private readonly JsonWebTokenConfigurationOptions _jwtConfiguration;
|
||||||
|
|
||||||
public LoginQueryHandler(AuthenticationService authenticationService)
|
public LoginQueryHandler(UnitOfWork unitOfWork,
|
||||||
|
PasswordHasherService passwordHasher,
|
||||||
|
IOptions<ConfigurationOptions> configurationOptions)
|
||||||
{
|
{
|
||||||
_authenticationService = authenticationService;
|
_unitOfWork = unitOfWork;
|
||||||
|
_passwordHasher = passwordHasher;
|
||||||
|
_jwtConfiguration = configurationOptions.Value.JsonWebToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TokensModel> Handle(
|
public async Task<TokensModel> Handle(
|
||||||
LoginQuery request, CancellationToken cancellationToken)
|
LoginQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return await _authenticationService.LoginAsync(
|
var account = await _unitOfWork.AccountRepository
|
||||||
request.Email, request.Password, cancellationToken);
|
.GetOneAsync(
|
||||||
|
a =>
|
||||||
|
a.Email == request.EmailOrUsername ||
|
||||||
|
a.Username == request.EmailOrUsername,
|
||||||
|
a => a.AccountRoles, cancellationToken);
|
||||||
|
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
throw new LoginException("No users registered with given email.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash = Convert.FromBase64String(account.PasswordHash);
|
||||||
|
var salt = Convert.FromBase64String(account.PasswordSalt);
|
||||||
|
var password = Encoding.UTF8.GetBytes(request.Password);
|
||||||
|
|
||||||
|
var isValidPassword = await _passwordHasher
|
||||||
|
.IsValidHashAsync(hash, password, salt, cancellationToken);
|
||||||
|
|
||||||
|
if (!isValidPassword)
|
||||||
|
{
|
||||||
|
throw new LoginException("Given password is incorrect.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var jwtSecurityToken = await CreateJwtAsync(account, cancellationToken);
|
||||||
|
var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
|
||||||
|
|
||||||
|
var refreshToken = (await _unitOfWork.RefreshTokenRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
e =>
|
||||||
|
e.AccountId == account.Id &&
|
||||||
|
e.RevocationTime == null &&
|
||||||
|
e.ExpirationTime > DateTimeOffset.UtcNow,
|
||||||
|
1, int.MaxValue, cancellationToken))
|
||||||
|
.Items.FirstOrDefault();
|
||||||
|
|
||||||
|
if (refreshToken == null)
|
||||||
|
{
|
||||||
|
refreshToken = CreateRefreshToken();
|
||||||
|
refreshToken.AccountId = account.Id;
|
||||||
|
|
||||||
|
await _unitOfWork.RefreshTokenRepository
|
||||||
|
.AddOneAsync(refreshToken, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return new TokensModel(accessToken, refreshToken.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<JwtSecurityToken> CreateJwtAsync(
|
||||||
|
Account account, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var roleIds = account.AccountRoles.Select(ar => ar.RoleId);
|
||||||
|
|
||||||
|
var roles = (await _unitOfWork.RoleRepository
|
||||||
|
.GetPageAsync(
|
||||||
|
r => roleIds.Contains(r.Id),
|
||||||
|
1, roleIds.Count(), cancellationToken))
|
||||||
|
.Items.Select(r => r.Value);
|
||||||
|
|
||||||
|
var roleClaims = new List<Claim>();
|
||||||
|
foreach (var role in roles)
|
||||||
|
{
|
||||||
|
roleClaims.Add(new Claim("roles", role.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims = new List<Claim>()
|
||||||
|
{
|
||||||
|
new Claim(JwtRegisteredClaimNames.Sub, account.Guid.ToString()),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Nickname, account.Username),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Email, account.Email)
|
||||||
|
}
|
||||||
|
.Union(roleClaims);
|
||||||
|
|
||||||
|
var expirationDateTimeUtc = DateTime.UtcNow.Add(
|
||||||
|
_jwtConfiguration.AccessTokenValidity);
|
||||||
|
|
||||||
|
var symmetricSecurityKey = new SymmetricSecurityKey(
|
||||||
|
Encoding.UTF8.GetBytes(_jwtConfiguration.IssuerSigningKey));
|
||||||
|
var signingCredentials = new SigningCredentials(
|
||||||
|
symmetricSecurityKey, SecurityAlgorithms.HmacSha256);
|
||||||
|
|
||||||
|
var jwtSecurityToken = new JwtSecurityToken(
|
||||||
|
issuer: _jwtConfiguration.Issuer,
|
||||||
|
audience: _jwtConfiguration.Audience,
|
||||||
|
claims: claims,
|
||||||
|
expires: expirationDateTimeUtc,
|
||||||
|
signingCredentials: signingCredentials);
|
||||||
|
|
||||||
|
return jwtSecurityToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RefreshToken CreateRefreshToken()
|
||||||
|
{
|
||||||
|
var token = RandomNumberGenerator.GetBytes(128 / 8);
|
||||||
|
|
||||||
|
return new RefreshToken
|
||||||
|
{
|
||||||
|
Guid = Guid.NewGuid(),
|
||||||
|
Value = Convert.ToBase64String(token),
|
||||||
|
CreationTime = DateTimeOffset.UtcNow,
|
||||||
|
ExpirationTime = DateTimeOffset.UtcNow.Add(
|
||||||
|
_jwtConfiguration.RefreshTokenValidity)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Queries.Login;
|
namespace cuqmbr.TravelGuide.Application.Authenticaion.Queries.Login;
|
||||||
|
|
||||||
public class LoginQueryValidator : AbstractValidator<LoginQuery>
|
public class LoginQueryValidator : AbstractValidator<LoginQuery>
|
||||||
{
|
{
|
||||||
public LoginQueryValidator()
|
public LoginQueryValidator(IStringLocalizer localizer)
|
||||||
{
|
{
|
||||||
RuleFor(v => v.Email)
|
RuleFor(v => v.EmailOrUsername)
|
||||||
.NotEmpty().WithMessage("Email address is required.");
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
|
||||||
RuleFor(v => v.Password)
|
RuleFor(v => v.Password)
|
||||||
.NotEmpty().WithMessage("Password is required.");
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
src/Application/Buses/BusDto.cs
Normal file
28
src/Application/Buses/BusDto.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses;
|
||||||
|
|
||||||
|
public sealed class BusDto : IMapFrom<Bus>
|
||||||
|
{
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public string Number { get; set; }
|
||||||
|
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public short Capacity { get; set; }
|
||||||
|
|
||||||
|
public Guid CompanyUuid { get; set; }
|
||||||
|
|
||||||
|
public void Mapping(MappingProfile profile)
|
||||||
|
{
|
||||||
|
profile.CreateMap<Bus, BusDto>()
|
||||||
|
.ForMember(
|
||||||
|
d => d.Uuid,
|
||||||
|
opt => opt.MapFrom(s => s.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.CompanyUuid,
|
||||||
|
opt => opt.MapFrom(s => s.Company.Guid));
|
||||||
|
}
|
||||||
|
}
|
14
src/Application/Buses/Commands/AddBus/AddBusCommand.cs
Normal file
14
src/Application/Buses/Commands/AddBus/AddBusCommand.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.AddBus;
|
||||||
|
|
||||||
|
public record AddBusCommand : IRequest<BusDto>
|
||||||
|
{
|
||||||
|
public string Number { get; set; }
|
||||||
|
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public short Capacity { get; set; }
|
||||||
|
|
||||||
|
public Guid CompanyGuid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.AddBus;
|
||||||
|
|
||||||
|
public class AddBusCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<AddBusCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public AddBusCommandAuthorizer(
|
||||||
|
SessionUserService sessionUserService,
|
||||||
|
UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(AddBusCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
var company = _unitOfWork.CompanyRepository
|
||||||
|
.GetOneAsync(
|
||||||
|
e => e.Guid == request.CompanyGuid, e => e.Account,
|
||||||
|
CancellationToken.None)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
UseRequirement(new MustBeObjectOwnerOrAdminRequirement
|
||||||
|
{
|
||||||
|
UserRoles = _sessionUserService.Roles,
|
||||||
|
RequiredGuid = company?.Account.Guid,
|
||||||
|
UserGuid = _sessionUserService.Guid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.AddBus;
|
||||||
|
|
||||||
|
public class AddBusCommandHandler :
|
||||||
|
IRequestHandler<AddBusCommand, BusDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public AddBusCommandHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BusDto> Handle(
|
||||||
|
AddBusCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.BusRepository.GetOneAsync(
|
||||||
|
e => e.Number == request.Number, cancellationToken);
|
||||||
|
|
||||||
|
if (entity != null)
|
||||||
|
{
|
||||||
|
throw new DuplicateEntityException(
|
||||||
|
"Bus with given number already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentEntity = await _unitOfWork.CompanyRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.CompanyGuid, cancellationToken);
|
||||||
|
|
||||||
|
if (parentEntity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException(
|
||||||
|
$"Parent entity with Guid: {request.CompanyGuid} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
entity = new Bus()
|
||||||
|
{
|
||||||
|
Number = request.Number,
|
||||||
|
Model = request.Model,
|
||||||
|
Capacity = request.Capacity,
|
||||||
|
CompanyId = parentEntity.Id,
|
||||||
|
Company = parentEntity
|
||||||
|
};
|
||||||
|
|
||||||
|
entity = await _unitOfWork.BusRepository.AddOneAsync(
|
||||||
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return _mapper.Map<BusDto>(entity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.AddBus;
|
||||||
|
|
||||||
|
public class AddBusCommandValidator : AbstractValidator<AddBusCommand>
|
||||||
|
{
|
||||||
|
public AddBusCommandValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
SessionCultureService cultureService)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Number)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MaximumLength(32)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
32));
|
||||||
|
|
||||||
|
RuleFor(v => v.Model)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
|
|
||||||
|
RuleFor(v => v.Capacity)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.CompanyGuid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.DeleteBus;
|
||||||
|
|
||||||
|
public record DeleteBusCommand : IRequest
|
||||||
|
{
|
||||||
|
public Guid Guid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.DeleteBus;
|
||||||
|
|
||||||
|
public class DeleteBusCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<DeleteBusCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public DeleteBusCommandAuthorizer(
|
||||||
|
SessionUserService sessionUserService,
|
||||||
|
UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(DeleteBusCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
var vehicel = _unitOfWork.VehicleRepository
|
||||||
|
.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, e => e.Company.Account,
|
||||||
|
CancellationToken.None)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
UseRequirement(new MustBeObjectOwnerOrAdminRequirement
|
||||||
|
{
|
||||||
|
UserRoles = _sessionUserService.Roles,
|
||||||
|
RequiredGuid = vehicel?.Company.Account.Guid,
|
||||||
|
UserGuid = _sessionUserService.Guid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.DeleteBus;
|
||||||
|
|
||||||
|
public class DeleteBusCommandHandler : IRequestHandler<DeleteBusCommand>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public DeleteBusCommandHandler(UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Handle(
|
||||||
|
DeleteBusCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.BusRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, cancellationToken);
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _unitOfWork.BusRepository.DeleteOneAsync(
|
||||||
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.DeleteBus;
|
||||||
|
|
||||||
|
public class DeleteBusCommandValidator : AbstractValidator<DeleteBusCommand>
|
||||||
|
{
|
||||||
|
public DeleteBusCommandValidator(IStringLocalizer localizer)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
16
src/Application/Buses/Commands/UpdateBus/UpdateBusCommand.cs
Normal file
16
src/Application/Buses/Commands/UpdateBus/UpdateBusCommand.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.UpdateBus;
|
||||||
|
|
||||||
|
public record UpdateBusCommand : IRequest<BusDto>
|
||||||
|
{
|
||||||
|
public Guid Guid { get; set; }
|
||||||
|
|
||||||
|
public string Number { get; set; }
|
||||||
|
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public short Capacity { get; set; }
|
||||||
|
|
||||||
|
public Guid CompanyGuid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.UpdateBus;
|
||||||
|
|
||||||
|
public class UpdateBusCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<UpdateBusCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public UpdateBusCommandAuthorizer(
|
||||||
|
SessionUserService sessionUserService,
|
||||||
|
UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(UpdateBusCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
var company = _unitOfWork.CompanyRepository
|
||||||
|
.GetOneAsync(
|
||||||
|
e => e.Guid == request.CompanyGuid, e => e.Account,
|
||||||
|
CancellationToken.None)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
UseRequirement(new MustBeObjectOwnerOrAdminRequirement
|
||||||
|
{
|
||||||
|
UserRoles = _sessionUserService.Roles,
|
||||||
|
RequiredGuid = company?.Account.Guid,
|
||||||
|
UserGuid = _sessionUserService.Guid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.UpdateBus;
|
||||||
|
|
||||||
|
public class UpdateBusCommandHandler :
|
||||||
|
IRequestHandler<UpdateBusCommand, BusDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public UpdateBusCommandHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BusDto> Handle(
|
||||||
|
UpdateBusCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.BusRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, cancellationToken);
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var duplicateEntity = await _unitOfWork.BusRepository.GetOneAsync(
|
||||||
|
e => e.Number == request.Number && e.Guid != request.Guid,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (duplicateEntity != null)
|
||||||
|
{
|
||||||
|
throw new DuplicateEntityException(
|
||||||
|
"Bus with given number already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentEntity = await _unitOfWork.CompanyRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.CompanyGuid, cancellationToken);
|
||||||
|
|
||||||
|
if (parentEntity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException(
|
||||||
|
$"Parent entity with Guid: {request.CompanyGuid} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.Number = request.Number;
|
||||||
|
entity.Model = request.Model;
|
||||||
|
entity.Capacity = request.Capacity;
|
||||||
|
entity.CompanyId = parentEntity.Id;
|
||||||
|
entity.Company = parentEntity;
|
||||||
|
|
||||||
|
entity = await _unitOfWork.BusRepository.UpdateOneAsync(
|
||||||
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return _mapper.Map<BusDto>(entity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Commands.UpdateBus;
|
||||||
|
|
||||||
|
public class UpdateBusCommandValidator : AbstractValidator<UpdateBusCommand>
|
||||||
|
{
|
||||||
|
public UpdateBusCommandValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
SessionCultureService cultureService)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.Number)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MaximumLength(32)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
32));
|
||||||
|
|
||||||
|
RuleFor(v => v.Model)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
|
|
||||||
|
RuleFor(v => v.Capacity)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
|
||||||
|
RuleFor(v => v.CompanyGuid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
8
src/Application/Buses/Queries/GetBus/GetBusQuery.cs
Normal file
8
src/Application/Buses/Queries/GetBus/GetBusQuery.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Queries.GetBus;
|
||||||
|
|
||||||
|
public record GetBusQuery : IRequest<BusDto>
|
||||||
|
{
|
||||||
|
public Guid Guid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Queries.GetBus;
|
||||||
|
|
||||||
|
public class GetBusQueryAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<GetBusQuery>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public GetBusQueryAuthorizer(
|
||||||
|
SessionUserService sessionUserService,
|
||||||
|
UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(GetBusQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
var vehicel = _unitOfWork.VehicleRepository
|
||||||
|
.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, e => e.Company.Account,
|
||||||
|
CancellationToken.None)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
UseRequirement(new MustBeObjectOwnerOrAdminRequirement
|
||||||
|
{
|
||||||
|
UserRoles = _sessionUserService.Roles,
|
||||||
|
RequiredGuid = vehicel?.Company.Account.Guid,
|
||||||
|
UserGuid = _sessionUserService.Guid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
39
src/Application/Buses/Queries/GetBus/GetBusQueryHandler.cs
Normal file
39
src/Application/Buses/Queries/GetBus/GetBusQueryHandler.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
using AutoMapper;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Queries.GetBus;
|
||||||
|
|
||||||
|
public class GetBusQueryHandler :
|
||||||
|
IRequestHandler<GetBusQuery, BusDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public GetBusQueryHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BusDto> Handle(
|
||||||
|
GetBusQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.BusRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.Guid, e => e.Company,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _mapper.Map<BusDto>(entity);
|
||||||
|
}
|
||||||
|
}
|
14
src/Application/Buses/Queries/GetBus/GetBusQueryValidator.cs
Normal file
14
src/Application/Buses/Queries/GetBus/GetBusQueryValidator.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Queries.GetBus;
|
||||||
|
|
||||||
|
public class GetBusQueryValidator : AbstractValidator<GetBusQuery>
|
||||||
|
{
|
||||||
|
public GetBusQueryValidator(IStringLocalizer localizer)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Guid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Queries.GetBusesPage;
|
||||||
|
|
||||||
|
public record GetBusesPageQuery : IRequest<PaginatedList<BusDto>>
|
||||||
|
{
|
||||||
|
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 Guid? CompanyGuid { get; set; }
|
||||||
|
|
||||||
|
public short? CapacityGreaterThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public short? CapacityLessThanOrEqualTo { get; set; }
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using MediatR.Behaviors.Authorization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Queries.GetBusesPage;
|
||||||
|
|
||||||
|
public class GetBusesPageQueryAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<GetBusesPageQuery>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public GetBusesPageQueryAuthorizer(
|
||||||
|
SessionUserService sessionUserService,
|
||||||
|
UnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(GetBusesPageQuery request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
var company = _unitOfWork.CompanyRepository
|
||||||
|
.GetOneAsync(
|
||||||
|
e => e.Guid == request.CompanyGuid, e => e.Account,
|
||||||
|
CancellationToken.None)
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
UseRequirement(new MustBeObjectOwnerOrAdminRequirement
|
||||||
|
{
|
||||||
|
UserRoles = _sessionUserService.Roles,
|
||||||
|
RequiredGuid = company?.Account.Guid,
|
||||||
|
UserGuid = _sessionUserService.Guid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Extensions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Queries.GetBusesPage;
|
||||||
|
|
||||||
|
public class GetBusesPageQueryHandler :
|
||||||
|
IRequestHandler<GetBusesPageQuery, PaginatedList<BusDto>>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public GetBusesPageQueryHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PaginatedList<BusDto>> Handle(
|
||||||
|
GetBusesPageQuery request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var paginatedList = await _unitOfWork.BusRepository.GetPageAsync(
|
||||||
|
e =>
|
||||||
|
(e.Number.ToLower().Contains(request.Search.ToLower()) ||
|
||||||
|
e.Model.ToLower().Contains(request.Search.ToLower())) &&
|
||||||
|
(request.CompanyGuid != null
|
||||||
|
? e.Company.Guid == request.CompanyGuid
|
||||||
|
: true) &&
|
||||||
|
(request.CapacityGreaterThanOrEqualTo != null
|
||||||
|
? e.Capacity >= request.CapacityGreaterThanOrEqualTo
|
||||||
|
: true) &&
|
||||||
|
(request.CapacityLessThanOrEqualTo != null
|
||||||
|
? e.Capacity <= request.CapacityLessThanOrEqualTo
|
||||||
|
: true),
|
||||||
|
e => e.Company,
|
||||||
|
request.PageNumber, request.PageSize,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
var mappedItems = _mapper
|
||||||
|
.ProjectTo<BusDto>(paginatedList.Items.AsQueryable());
|
||||||
|
|
||||||
|
mappedItems = QueryableExtension<BusDto>
|
||||||
|
.ApplySort(mappedItems, request.Sort);
|
||||||
|
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return new PaginatedList<BusDto>(
|
||||||
|
mappedItems.ToList(),
|
||||||
|
paginatedList.TotalCount, request.PageNumber,
|
||||||
|
request.PageSize);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.Queries.GetBusesPage;
|
||||||
|
|
||||||
|
public class GetBusesPageQueryValidator : AbstractValidator<GetBusesPageQuery>
|
||||||
|
{
|
||||||
|
public GetBusesPageQueryValidator(
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
12
src/Application/Buses/ViewModels/AddBusViewModel.cs
Normal file
12
src/Application/Buses/ViewModels/AddBusViewModel.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.ViewModels;
|
||||||
|
|
||||||
|
public sealed class AddBusViewModel
|
||||||
|
{
|
||||||
|
public string Number { get; set; }
|
||||||
|
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public short Capacity { get; set; }
|
||||||
|
|
||||||
|
public Guid CompanyUuid { get; set; }
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.ViewModels;
|
||||||
|
|
||||||
|
public sealed class GetBusesPageFilterViewModel
|
||||||
|
{
|
||||||
|
public Guid? CompanyUuid { get; set; }
|
||||||
|
|
||||||
|
public short? CapacityGreaterThanOrEqualTo { get; set; }
|
||||||
|
|
||||||
|
public short? CapacityLessThanOrEqualTo { get; set; }
|
||||||
|
}
|
12
src/Application/Buses/ViewModels/UpdateBusViewModel.cs
Normal file
12
src/Application/Buses/ViewModels/UpdateBusViewModel.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace cuqmbr.TravelGuide.Application.Buses.ViewModels;
|
||||||
|
|
||||||
|
public sealed class UpdateBusViewModel
|
||||||
|
{
|
||||||
|
public string Number { get; set; }
|
||||||
|
|
||||||
|
public string Model { get; set; }
|
||||||
|
|
||||||
|
public short Capacity { get; set; }
|
||||||
|
|
||||||
|
public Guid CompanyUuid { get; set; }
|
||||||
|
}
|
39
src/Application/Cities/CityDto.cs
Normal file
39
src/Application/Cities/CityDto.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Cities;
|
||||||
|
|
||||||
|
public sealed class CityDto : IMapFrom<City>
|
||||||
|
{
|
||||||
|
public Guid Uuid { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public Guid CountryUuid { get; set; }
|
||||||
|
|
||||||
|
public string CountryName { get; set; }
|
||||||
|
|
||||||
|
public Guid RegionUuid { get; set; }
|
||||||
|
|
||||||
|
public string RegionName { get; set; }
|
||||||
|
|
||||||
|
public void Mapping(MappingProfile profile)
|
||||||
|
{
|
||||||
|
profile.CreateMap<City, CityDto>()
|
||||||
|
.ForMember(
|
||||||
|
d => d.Uuid,
|
||||||
|
opt => opt.MapFrom(s => s.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.CountryUuid,
|
||||||
|
opt => opt.MapFrom(s => s.Region.Country.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.CountryName,
|
||||||
|
opt => opt.MapFrom(s => s.Region.Country.Name))
|
||||||
|
.ForMember(
|
||||||
|
d => d.RegionUuid,
|
||||||
|
opt => opt.MapFrom(s => s.Region.Guid))
|
||||||
|
.ForMember(
|
||||||
|
d => d.RegionName,
|
||||||
|
opt => opt.MapFrom(s => s.Region.Name));
|
||||||
|
}
|
||||||
|
}
|
10
src/Application/Cities/Commands/AddCity/AddCityCommand.cs
Normal file
10
src/Application/Cities/Commands/AddCity/AddCityCommand.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Cities.Commands.AddCity;
|
||||||
|
|
||||||
|
public record AddCityCommand : IRequest<CityDto>
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public Guid RegionGuid { 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.Cities.Commands.AddCity;
|
||||||
|
|
||||||
|
public class AddCityCommandAuthorizer :
|
||||||
|
AbstractRequestAuthorizer<AddCityCommand>
|
||||||
|
{
|
||||||
|
private readonly SessionUserService _sessionUserService;
|
||||||
|
|
||||||
|
public AddCityCommandAuthorizer(SessionUserService sessionUserService)
|
||||||
|
{
|
||||||
|
_sessionUserService = sessionUserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BuildPolicy(AddCityCommand request)
|
||||||
|
{
|
||||||
|
UseRequirement(new MustBeAuthenticatedRequirement
|
||||||
|
{
|
||||||
|
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||||
|
});
|
||||||
|
|
||||||
|
UseRequirement(new MustBeInAnyOfRolesRequirement
|
||||||
|
{
|
||||||
|
RequiredRoles = [IdentityRole.Administrator],
|
||||||
|
UserRoles = _sessionUserService.Roles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
using MediatR;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Persistence;
|
||||||
|
using cuqmbr.TravelGuide.Domain.Entities;
|
||||||
|
using AutoMapper;
|
||||||
|
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Cities.Commands.AddCity;
|
||||||
|
|
||||||
|
public class AddCityCommandHandler :
|
||||||
|
IRequestHandler<AddCityCommand, CityDto>
|
||||||
|
{
|
||||||
|
private readonly UnitOfWork _unitOfWork;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public AddCityCommandHandler(
|
||||||
|
UnitOfWork unitOfWork,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CityDto> Handle(
|
||||||
|
AddCityCommand request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _unitOfWork.CityRepository.GetOneAsync(
|
||||||
|
e => e.Name == request.Name && e.Region.Guid == request.RegionGuid,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (entity != null)
|
||||||
|
{
|
||||||
|
throw new DuplicateEntityException(
|
||||||
|
"City with given name already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentEntity = await _unitOfWork.RegionRepository.GetOneAsync(
|
||||||
|
e => e.Guid == request.RegionGuid, e => e.Country,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (parentEntity == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException(
|
||||||
|
$"Parent entity with Guid: {request.RegionGuid} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
entity = new City()
|
||||||
|
{
|
||||||
|
Name = request.Name,
|
||||||
|
RegionId = parentEntity.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
entity = await _unitOfWork.CityRepository.AddOneAsync(
|
||||||
|
entity, cancellationToken);
|
||||||
|
|
||||||
|
await _unitOfWork.SaveAsync(cancellationToken);
|
||||||
|
_unitOfWork.Dispose();
|
||||||
|
|
||||||
|
return _mapper.Map<CityDto>(entity);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
|
||||||
|
namespace cuqmbr.TravelGuide.Application.Cities.Commands.AddCity;
|
||||||
|
|
||||||
|
public class AddCityCommandValidator : AbstractValidator<AddCityCommand>
|
||||||
|
{
|
||||||
|
public AddCityCommandValidator(
|
||||||
|
IStringLocalizer localizer,
|
||||||
|
SessionCultureService cultureService)
|
||||||
|
{
|
||||||
|
RuleFor(v => v.Name)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||||
|
.MaximumLength(64)
|
||||||
|
.WithMessage(
|
||||||
|
String.Format(
|
||||||
|
cultureService.Culture,
|
||||||
|
localizer["FluentValidation.MaximumLength"],
|
||||||
|
64));
|
||||||
|
|
||||||
|
RuleFor(v => v.RegionGuid)
|
||||||
|
.NotEmpty()
|
||||||
|
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user