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-dependency-path: |
|
||||
src/Application/packages.lock.json
|
||||
src/Identity/packages.lock.json
|
||||
src/Infrastructure/packages.lock.json
|
||||
src/Persistence/packages.lock.json
|
||||
src/Configuration/packages.lock.json
|
||||
|
@ -13,8 +13,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Infra
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpApi", "src\HttpApi\HttpApi.csproj", "{4431B3CB-A5F2-447A-8BC7-9DC3DA9E6A6D}"
|
||||
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}"
|
||||
EndProject
|
||||
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}.Release|Any CPU.ActiveCfg = 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.Build.0 = Debug|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="MediatR" Version="12.4.1" />
|
||||
<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.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" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -4,6 +4,8 @@ namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.Register;
|
||||
|
||||
public record RegisterCommand : IRequest
|
||||
{
|
||||
public string Username { get; set; }
|
||||
|
||||
public string Email { 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;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.Register;
|
||||
|
||||
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(
|
||||
RegisterCommand request, CancellationToken cancellationToken)
|
||||
public async Task Handle(RegisterCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await _authenticationService.RegisterAsync(
|
||||
request.Email, request.Password, cancellationToken);
|
||||
var datastoreAccount = await _unitOfWork.AccountRepository
|
||||
.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 Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.Register;
|
||||
|
||||
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)
|
||||
.NotEmpty()
|
||||
.WithMessage("Email address is required.")
|
||||
.Matches(@"\b[\w\.-]+@[\w\.-]+\.\w{2,4}\b")
|
||||
.WithMessage("Email address is invalid.");
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||
.IsEmail()
|
||||
.WithMessage(localizer["FluentValidation.IsEmail"]);
|
||||
|
||||
RuleFor(v => v.Password)
|
||||
.NotEmpty()
|
||||
.WithMessage("Password is required.")
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||
.MinimumLength(8)
|
||||
.WithMessage("Password must be at least 8 characters long.")
|
||||
.MaximumLength(64)
|
||||
.WithMessage("Password must be at most 64 characters long.")
|
||||
.Matches(@"(?=.*[A-Z]).*")
|
||||
.WithMessage("Password must contain at least one uppercase letter.")
|
||||
.Matches(@"(?=.*[a-z]).*")
|
||||
.WithMessage("Password must contain at least one lowercase letter.")
|
||||
.Matches(@"(?=.*[\d]).*")
|
||||
.WithMessage("Password must contain at least one digit.")
|
||||
.Matches(@"(?=.*[!@#$%^&*()]).*")
|
||||
.WithMessage("Password must contain at least one of the following special charactters: !@#$%^&*().");
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.MinimumLength"],
|
||||
8))
|
||||
.MaximumLength(256)
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.MaximumLength"],
|
||||
256));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RenewAccessToken;
|
||||
@ -7,18 +6,8 @@ namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RenewAccessToken
|
||||
public class RenewAccessTokenCommandAuthorizer :
|
||||
AbstractRequestAuthorizer<RenewAccessTokenCommand>
|
||||
{
|
||||
private readonly SessionUserService _sessionUserService;
|
||||
|
||||
public RenewAccessTokenCommandAuthorizer(SessionUserService currentUserService)
|
||||
{
|
||||
_sessionUserService = currentUserService;
|
||||
}
|
||||
|
||||
public override void BuildPolicy(RenewAccessTokenCommand request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated = _sessionUserService.IsAuthenticated
|
||||
});
|
||||
UseRequirement(new AllowAllRequirement());
|
||||
}
|
||||
}
|
||||
|
@ -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 Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RenewAccessToken;
|
||||
|
||||
public class RenewAccessTokenCommandHandler :
|
||||
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(
|
||||
RenewAccessTokenCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _authenticationService.RenewAccessTokenAsync(
|
||||
request.RefreshToken, cancellationToken);
|
||||
var refreshToken = (await _unitOfWork.RefreshTokenRepository
|
||||
.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.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Application.Common.Services;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
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;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RevokeRefreshToken;
|
||||
@ -6,17 +7,36 @@ namespace cuqmbr.TravelGuide.Application.Authenticaion.Commands.RevokeRefreshTok
|
||||
public class RevokeRefreshTokenCommandHandler :
|
||||
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(
|
||||
RevokeRefreshTokenCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
await _authenticationService.RevokeRefreshTokenAsync(
|
||||
request.RefreshToken, cancellationToken);
|
||||
var refreshToken = (await _unitOfWork.RefreshTokenRepository
|
||||
.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 string Email { get; set; }
|
||||
public string EmailOrUsername { 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 Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Queries.Login;
|
||||
|
||||
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(
|
||||
LoginQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
return await _authenticationService.LoginAsync(
|
||||
request.Email, request.Password, cancellationToken);
|
||||
var account = await _unitOfWork.AccountRepository
|
||||
.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 Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Authenticaion.Queries.Login;
|
||||
|
||||
public class LoginQueryValidator : AbstractValidator<LoginQuery>
|
||||
{
|
||||
public LoginQueryValidator()
|
||||
public LoginQueryValidator(IStringLocalizer localizer)
|
||||
{
|
||||
RuleFor(v => v.Email)
|
||||
.NotEmpty().WithMessage("Email address is required.");
|
||||
RuleFor(v => v.EmailOrUsername)
|
||||
.NotEmpty()
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||
|
||||
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