add route entity management
This commit is contained in:
parent
d500d1f84c
commit
fdf147fe83
@ -10,9 +10,24 @@ public class ValidationException : Exception
|
||||
Errors = new Dictionary<string, string[]>();
|
||||
}
|
||||
|
||||
public ValidationException(string message) : base(message)
|
||||
{
|
||||
Errors = new Dictionary<string, string[]>();
|
||||
}
|
||||
|
||||
public ValidationException(IEnumerable<ValidationFailure> failures)
|
||||
: this()
|
||||
{
|
||||
// TODO: Make serialized dictionary look more like this
|
||||
// "errors": {
|
||||
// "viewModel": [
|
||||
// "The viewModel field is required."
|
||||
// ],
|
||||
// "$.addresses[0].order": [
|
||||
// "The JSON value could not be converted to System.Int16. Path: $.addresses[0].order | LineNumber: 5 | BytePositionInLine: 26."
|
||||
// ]
|
||||
// },
|
||||
|
||||
Errors = failures
|
||||
.GroupBy(f => f.PropertyName, f => f.ErrorMessage)
|
||||
.ToDictionary(fg => fg.Key, fg => fg.ToArray());
|
||||
|
@ -0,0 +1,6 @@
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Common.Interfaces
|
||||
.Persistence.Repositories;
|
||||
|
||||
public interface RouteRepository : BaseRepository<Route> { }
|
@ -12,6 +12,8 @@ public interface UnitOfWork : IDisposable
|
||||
|
||||
AddressRepository AddressRepository { get; }
|
||||
|
||||
RouteRepository RouteRepository { get; }
|
||||
|
||||
int Save();
|
||||
|
||||
Task<int> SaveAsync(CancellationToken cancellationToken);
|
||||
|
@ -6,6 +6,10 @@
|
||||
"LessThanOrEqualTo": "Must be less than or equal to {0:G}.",
|
||||
"MustBeInEnum": "Must be one of the following: {0}."
|
||||
},
|
||||
"Validation": {
|
||||
"DistinctOrder": "Must have distinct order values.",
|
||||
"SameVehicleType": "Must have the same vehicle type."
|
||||
},
|
||||
"ExceptionHandling": {
|
||||
"ValidationException": {
|
||||
"Title": "One or more validation errors occurred.",
|
||||
|
14
src/Application/Routes/Commands/AddRoute/AddRouteCommand.cs
Normal file
14
src/Application/Routes/Commands/AddRoute/AddRouteCommand.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Routes.Models;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.AddRoute;
|
||||
|
||||
public record AddRouteCommand : IRequest<RouteDto>
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public VehicleType VehicleType { get; set; }
|
||||
|
||||
public ICollection<RouteAddressModel> Addresses { get; set; }
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.AddRoute;
|
||||
|
||||
public class AddRouteCommandAuthorizer :
|
||||
AbstractRequestAuthorizer<AddRouteCommand>
|
||||
{
|
||||
private readonly SessionUserService _sessionUserService;
|
||||
|
||||
public AddRouteCommandAuthorizer(SessionUserService sessionUserService)
|
||||
{
|
||||
_sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public override void BuildPolicy(AddRouteCommand request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||
});
|
||||
|
||||
UseRequirement(new MustBeInRolesRequirement
|
||||
{
|
||||
RequiredRoles = [IdentityRole.Administrator],
|
||||
UserRoles = _sessionUserService.Roles
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
using AutoMapper;
|
||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using FluentValidation.Results;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.AddRoute;
|
||||
|
||||
public class AddRouteCommandHandler :
|
||||
IRequestHandler<AddRouteCommand, RouteDto>
|
||||
{
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
|
||||
public AddRouteCommandHandler(
|
||||
UnitOfWork unitOfWork,
|
||||
IMapper mapper,
|
||||
IStringLocalizer localizer)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public async Task<RouteDto> Handle(
|
||||
AddRouteCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var page = await _unitOfWork.AddressRepository.GetPageAsync(
|
||||
e => request.Addresses.Select(a => a.Guid).Contains(e.Guid),
|
||||
e => e.City.Region.Country,
|
||||
1, request.Addresses.Count, cancellationToken);
|
||||
|
||||
var invalidVehicleTypeAddress =
|
||||
page.Items.FirstOrDefault(a => a.VehicleType != request.VehicleType);
|
||||
if (invalidVehicleTypeAddress != null)
|
||||
{
|
||||
throw new ValidationException(
|
||||
new List<ValidationFailure>
|
||||
{
|
||||
new()
|
||||
{
|
||||
PropertyName = nameof(request.Addresses),
|
||||
ErrorMessage = _localizer["Validation.SameVehicleType"]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var pageContainsAllRequestedAddresses =
|
||||
request.Addresses.Select(e => e.Guid)
|
||||
.All(e => page.Items.Select(e => e.Guid).Contains(e));
|
||||
if (!pageContainsAllRequestedAddresses)
|
||||
{
|
||||
var notFoundCount = request.Addresses.Count - page.TotalCount;
|
||||
throw new NotFoundException(
|
||||
$"{notFoundCount} addresses was not found.");
|
||||
}
|
||||
|
||||
var entity = new Route()
|
||||
{
|
||||
Name = request.Name,
|
||||
VehicleType = request.VehicleType,
|
||||
RouteAddresses = request.Addresses.Select(
|
||||
e => new RouteAddress()
|
||||
{
|
||||
Order = e.Order,
|
||||
AddressId = page.Items.Single(i => i.Guid == e.Guid).Id
|
||||
})
|
||||
.OrderBy(e => e.Order)
|
||||
.ToArray()
|
||||
};
|
||||
|
||||
entity = await _unitOfWork.RouteRepository.AddOneAsync(
|
||||
entity, cancellationToken);
|
||||
|
||||
await _unitOfWork.SaveAsync(cancellationToken);
|
||||
_unitOfWork.Dispose();
|
||||
|
||||
return _mapper.Map<RouteDto>(entity);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.AddRoute;
|
||||
|
||||
public class AddRouteCommandValidator : AbstractValidator<AddRouteCommand>
|
||||
{
|
||||
public AddRouteCommandValidator(
|
||||
IStringLocalizer localizer,
|
||||
CultureService cultureService)
|
||||
{
|
||||
RuleFor(v => v.Name)
|
||||
.NotEmpty()
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"])
|
||||
.MaximumLength(64)
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.MaximumLength"],
|
||||
64));
|
||||
|
||||
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.Addresses.Count)
|
||||
.GreaterThanOrEqualTo(2)
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||
2));
|
||||
|
||||
RuleFor(v => v.Addresses)
|
||||
.Must((v, a) => a.DistinctBy(e => e.Order).Count() == a.Count())
|
||||
.WithMessage(localizer["Validation.DistinctOrder"]);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using MediatR;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.DeleteRoute;
|
||||
|
||||
public record DeleteRouteCommand : IRequest
|
||||
{
|
||||
public Guid Guid { get; set; }
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.DeleteRoute;
|
||||
|
||||
public class DeleteRouteCommandAuthorizer :
|
||||
AbstractRequestAuthorizer<DeleteRouteCommand>
|
||||
{
|
||||
private readonly SessionUserService _sessionUserService;
|
||||
|
||||
public DeleteRouteCommandAuthorizer(SessionUserService sessionUserService)
|
||||
{
|
||||
_sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public override void BuildPolicy(DeleteRouteCommand request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||
});
|
||||
|
||||
UseRequirement(new MustBeInRolesRequirement
|
||||
{
|
||||
RequiredRoles = [IdentityRole.Administrator],
|
||||
UserRoles = _sessionUserService.Roles
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.DeleteRoute;
|
||||
|
||||
public class DeleteRouteCommandHandler : IRequestHandler<DeleteRouteCommand>
|
||||
{
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
|
||||
public DeleteRouteCommandHandler(UnitOfWork unitOfWork)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task Handle(
|
||||
DeleteRouteCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _unitOfWork.RouteRepository.GetOneAsync(
|
||||
e => e.Guid == request.Guid, cancellationToken);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// TODO: Check for Vehicles that using this route in Enrollments
|
||||
// Delete if there are no such Vehicles
|
||||
|
||||
await _unitOfWork.RouteRepository.DeleteOneAsync(
|
||||
entity, cancellationToken);
|
||||
|
||||
await _unitOfWork.SaveAsync(cancellationToken);
|
||||
_unitOfWork.Dispose();
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.DeleteRoute;
|
||||
|
||||
public class DeleteRouteCommandValidator : AbstractValidator<DeleteRouteCommand>
|
||||
{
|
||||
public DeleteRouteCommandValidator(IStringLocalizer localizer)
|
||||
{
|
||||
RuleFor(v => v.Guid)
|
||||
.NotEmpty()
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using cuqmbr.TravelGuide.Application.Routes.Models;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.UpdateRoute;
|
||||
|
||||
public record UpdateRouteCommand : IRequest<RouteDto>
|
||||
{
|
||||
public Guid Guid { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public VehicleType VehicleType { get; set; }
|
||||
|
||||
public ICollection<RouteAddressModel> Addresses { get; set; }
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.UpdateRoute;
|
||||
|
||||
public class UpdateRouteCommandAuthorizer :
|
||||
AbstractRequestAuthorizer<UpdateRouteCommand>
|
||||
{
|
||||
private readonly SessionUserService _sessionUserService;
|
||||
|
||||
public UpdateRouteCommandAuthorizer(SessionUserService sessionUserService)
|
||||
{
|
||||
_sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public override void BuildPolicy(UpdateRouteCommand request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||
});
|
||||
|
||||
UseRequirement(new MustBeInRolesRequirement
|
||||
{
|
||||
RequiredRoles = [IdentityRole.Administrator],
|
||||
UserRoles = _sessionUserService.Roles
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using AutoMapper;
|
||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.UpdateRoute;
|
||||
|
||||
public class UpdateRouteCommandHandler :
|
||||
IRequestHandler<UpdateRouteCommand, RouteDto>
|
||||
{
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
public IStringLocalizer _localizer { get; set; }
|
||||
|
||||
public UpdateRouteCommandHandler(
|
||||
UnitOfWork unitOfWork,
|
||||
IMapper mapper,
|
||||
IStringLocalizer localizer)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
public async Task<RouteDto> Handle(
|
||||
UpdateRouteCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _unitOfWork.RouteRepository.GetOneAsync(
|
||||
e => e.Guid == request.Guid,
|
||||
e => e.RouteAddresses,
|
||||
cancellationToken);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var page = await _unitOfWork.AddressRepository.GetPageAsync(
|
||||
e => request.Addresses.Select(a => a.Guid).Contains(e.Guid),
|
||||
e => e.City.Region.Country,
|
||||
1, request.Addresses.Count, cancellationToken);
|
||||
|
||||
var invalidVehicleTypeAddress =
|
||||
page.Items.FirstOrDefault(a => a.VehicleType != request.VehicleType);
|
||||
if (invalidVehicleTypeAddress != null)
|
||||
{
|
||||
throw new ValidationException(
|
||||
new List<ValidationFailure>
|
||||
{
|
||||
new()
|
||||
{
|
||||
PropertyName = nameof(request.Addresses),
|
||||
ErrorMessage = _localizer["Validation.SameVehicleType"]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var pageContainsAllRequestedAddresses =
|
||||
request.Addresses.Select(e => e.Guid)
|
||||
.All(e => page.Items.Select(e => e.Guid).Contains(e));
|
||||
if (!pageContainsAllRequestedAddresses)
|
||||
{
|
||||
var notFoundCount = request.Addresses.Count - page.TotalCount;
|
||||
throw new NotFoundException(
|
||||
$"{notFoundCount} addresses was not found.");
|
||||
}
|
||||
|
||||
|
||||
entity.Guid = request.Guid;
|
||||
entity.Name = request.Name;
|
||||
entity.VehicleType = request.VehicleType;
|
||||
|
||||
|
||||
var requestRouteAddresses = request.Addresses.Select(
|
||||
e => new RouteAddress()
|
||||
{
|
||||
Order = e.Order,
|
||||
AddressId = page.Items.Single(i => i.Guid == e.Guid).Id
|
||||
});
|
||||
|
||||
var commonRouteAddresses = entity.RouteAddresses.IntersectBy(
|
||||
requestRouteAddresses.Select(ra => (ra.Order, ra.AddressId)),
|
||||
ra => (ra.Order, ra.AddressId));
|
||||
|
||||
var newRouteAddresses = requestRouteAddresses.ExceptBy(
|
||||
entity.RouteAddresses.Select(ra => (ra.Order, ra.AddressId)),
|
||||
ra => (ra.Order, ra.AddressId));
|
||||
|
||||
var combinedRouteAddresses = commonRouteAddresses.UnionBy(
|
||||
newRouteAddresses, ra => (ra.Order, ra.AddressId));
|
||||
|
||||
entity.RouteAddresses = combinedRouteAddresses
|
||||
.OrderBy(e => e.Order).ToList();
|
||||
|
||||
|
||||
entity = await _unitOfWork.RouteRepository.UpdateOneAsync(
|
||||
entity, cancellationToken);
|
||||
|
||||
await _unitOfWork.SaveAsync(cancellationToken);
|
||||
_unitOfWork.Dispose();
|
||||
|
||||
return _mapper.Map<RouteDto>(entity);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Commands.UpdateRoute;
|
||||
|
||||
public class UpdateRouteCommandValidator : AbstractValidator<UpdateRouteCommand>
|
||||
{
|
||||
public UpdateRouteCommandValidator(
|
||||
IStringLocalizer localizer,
|
||||
CultureService 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.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.Addresses.Count)
|
||||
.GreaterThanOrEqualTo(2)
|
||||
.WithMessage(
|
||||
String.Format(
|
||||
cultureService.Culture,
|
||||
localizer["FluentValidation.GreaterThanOrEqualTo"],
|
||||
2));
|
||||
|
||||
RuleFor(v => v.Addresses)
|
||||
.Must((v, a) => a.DistinctBy(e => e.Order).Count() == a.Count())
|
||||
.WithMessage(localizer["Validation.DistinctOrder"]);
|
||||
}
|
||||
}
|
8
src/Application/Routes/Models/RouteAddressModel.cs
Normal file
8
src/Application/Routes/Models/RouteAddressModel.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Models;
|
||||
|
||||
public sealed class RouteAddressModel
|
||||
{
|
||||
public short Order { get; set; }
|
||||
|
||||
public Guid Guid { get; set; }
|
||||
}
|
8
src/Application/Routes/Queries/GetRoute/GetRouteQuery.cs
Normal file
8
src/Application/Routes/Queries/GetRoute/GetRouteQuery.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using MediatR;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Queries.GetRoute;
|
||||
|
||||
public record GetRouteQuery : IRequest<RouteDto>
|
||||
{
|
||||
public Guid Guid { get; set; }
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Queries.GetRoute;
|
||||
|
||||
public class GetRouteQueryAuthorizer :
|
||||
AbstractRequestAuthorizer<GetRouteQuery>
|
||||
{
|
||||
private readonly SessionUserService _sessionUserService;
|
||||
|
||||
public GetRouteQueryAuthorizer(SessionUserService sessionUserService)
|
||||
{
|
||||
_sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public override void BuildPolicy(GetRouteQuery request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||
});
|
||||
|
||||
UseRequirement(new MustBeInRolesRequirement
|
||||
{
|
||||
RequiredRoles = [IdentityRole.Administrator],
|
||||
UserRoles = _sessionUserService.Roles
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||
using AutoMapper;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Queries.GetRoute;
|
||||
|
||||
public class GetRouteQueryHandler :
|
||||
IRequestHandler<GetRouteQuery, RouteDto>
|
||||
{
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public GetRouteQueryHandler(
|
||||
UnitOfWork unitOfWork,
|
||||
IMapper mapper)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<RouteDto> Handle(
|
||||
GetRouteQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var entity = await _unitOfWork.RouteRepository.GetOneAsync(
|
||||
e => e.Guid == request.Guid, e => e.RouteAddresses,
|
||||
cancellationToken);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// TODO: Find a way to include through lists
|
||||
var addresses = await _unitOfWork.AddressRepository.GetPageAsync(
|
||||
e => entity.RouteAddresses.Select(ra => ra.AddressId).Contains(e.Id),
|
||||
e => e.City.Region.Country,
|
||||
1, entity.RouteAddresses.Count, cancellationToken);
|
||||
|
||||
entity.RouteAddresses = entity.RouteAddresses.Select(
|
||||
e => new RouteAddress()
|
||||
{
|
||||
Id = e.Id,
|
||||
Guid = e.Guid,
|
||||
Order = e.Order,
|
||||
RouteId = e.RouteId,
|
||||
Route = e.Route,
|
||||
AddressId = e.AddressId,
|
||||
Address = addresses.Items.First(a => a.Id == e.AddressId)
|
||||
})
|
||||
.OrderBy(e => e.Order)
|
||||
.ToArray();
|
||||
|
||||
_unitOfWork.Dispose();
|
||||
|
||||
return _mapper.Map<RouteDto>(entity);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Queries.GetRoute;
|
||||
|
||||
public class GetRouteQueryValidator : AbstractValidator<GetRouteQuery>
|
||||
{
|
||||
public GetRouteQueryValidator(IStringLocalizer localizer)
|
||||
{
|
||||
RuleFor(v => v.Guid)
|
||||
.NotEmpty()
|
||||
.WithMessage(localizer["FluentValidation.NotEmpty"]);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using MediatR;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Queries.GetRoutesPage;
|
||||
|
||||
public record GetRoutesPageQuery : IRequest<PaginatedList<RouteDto>>
|
||||
{
|
||||
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 VehicleType? VehicleType { get; set; }
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Authorization;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using MediatR.Behaviors.Authorization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Queries.GetRoutesPage;
|
||||
|
||||
public class GetRoutesPageQueryAuthorizer :
|
||||
AbstractRequestAuthorizer<GetRoutesPageQuery>
|
||||
{
|
||||
private readonly SessionUserService _sessionUserService;
|
||||
|
||||
public GetRoutesPageQueryAuthorizer(SessionUserService sessionUserService)
|
||||
{
|
||||
_sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public override void BuildPolicy(GetRoutesPageQuery request)
|
||||
{
|
||||
UseRequirement(new MustBeAuthenticatedRequirement
|
||||
{
|
||||
IsAuthenticated= _sessionUserService.IsAuthenticated
|
||||
});
|
||||
|
||||
UseRequirement(new MustBeInRolesRequirement
|
||||
{
|
||||
RequiredRoles = [IdentityRole.Administrator],
|
||||
UserRoles = _sessionUserService.Roles
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
using MediatR;
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence;
|
||||
using AutoMapper;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using cuqmbr.TravelGuide.Application.Common.Extensions;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Queries.GetRoutesPage;
|
||||
|
||||
public class GetRoutesPageQueryHandler :
|
||||
IRequestHandler<GetRoutesPageQuery, PaginatedList<RouteDto>>
|
||||
{
|
||||
private readonly UnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public GetRoutesPageQueryHandler(
|
||||
UnitOfWork unitOfWork,
|
||||
IMapper mapper)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<PaginatedList<RouteDto>> Handle(
|
||||
GetRoutesPageQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var paginatedList = await _unitOfWork.RouteRepository.GetPageAsync(
|
||||
e =>
|
||||
e.Name.ToLower().Contains(request.Search.ToLower()) &&
|
||||
(request.VehicleType != null
|
||||
? e.VehicleType == request.VehicleType
|
||||
: true),
|
||||
e => e.RouteAddresses,
|
||||
request.PageNumber, request.PageSize,
|
||||
cancellationToken);
|
||||
|
||||
foreach (var route in paginatedList.Items)
|
||||
{
|
||||
// TODO: Find a way to include through lists
|
||||
var addresses = await _unitOfWork.AddressRepository.GetPageAsync(
|
||||
e => route.RouteAddresses.Select(ra => ra.AddressId).Contains(e.Id),
|
||||
e => e.City.Region.Country,
|
||||
1, route.RouteAddresses.Count, cancellationToken);
|
||||
|
||||
route.RouteAddresses = route.RouteAddresses.Select(
|
||||
e => new RouteAddress()
|
||||
{
|
||||
Id = e.Id,
|
||||
Guid = e.Guid,
|
||||
Order = e.Order,
|
||||
RouteId = e.RouteId,
|
||||
Route = e.Route,
|
||||
AddressId = e.AddressId,
|
||||
Address = addresses.Items.First(a => a.Id == e.AddressId)
|
||||
})
|
||||
.OrderBy(e => e.Order)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
var mappedItems = _mapper
|
||||
.ProjectTo<RouteDto>(paginatedList.Items.AsQueryable());
|
||||
|
||||
mappedItems = QueryableExtension<RouteDto>
|
||||
.ApplySort(mappedItems, request.Sort);
|
||||
|
||||
_unitOfWork.Dispose();
|
||||
|
||||
return new PaginatedList<RouteDto>(
|
||||
mappedItems.ToList(),
|
||||
paginatedList.TotalCount, request.PageNumber,
|
||||
request.PageSize);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Services;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using FluentValidation;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.Queries.GetRoutesPage;
|
||||
|
||||
public class GetRoutesPageQueryValidator : AbstractValidator<GetRoutesPageQuery>
|
||||
{
|
||||
public GetRoutesPageQueryValidator(
|
||||
IStringLocalizer localizer,
|
||||
CultureService 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));
|
||||
}
|
||||
}
|
70
src/Application/Routes/RouteAddressDto.cs
Normal file
70
src/Application/Routes/RouteAddressDto.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes;
|
||||
|
||||
public sealed class RouteAddressDto : IMapFrom<RouteAddress>
|
||||
{
|
||||
public short Order { get; set; }
|
||||
|
||||
|
||||
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<RouteAddress, RouteAddressDto>()
|
||||
.ForMember(
|
||||
d => d.Uuid,
|
||||
opt => opt.MapFrom(s => s.Address.Guid))
|
||||
.ForMember(
|
||||
d => d.Name,
|
||||
opt => opt.MapFrom(s => s.Address.Name))
|
||||
.ForMember(
|
||||
d => d.Longitude,
|
||||
opt => opt.MapFrom(s => s.Address.Longitude))
|
||||
.ForMember(
|
||||
d => d.Latitude,
|
||||
opt => opt.MapFrom(s => s.Address.Latitude))
|
||||
.ForMember(
|
||||
d => d.VehicleType,
|
||||
opt => opt.MapFrom(s => s.Address.VehicleType.Name))
|
||||
.ForMember(
|
||||
d => d.CityUuid,
|
||||
opt => opt.MapFrom(s => s.Address.City.Guid))
|
||||
.ForMember(
|
||||
d => d.CityName,
|
||||
opt => opt.MapFrom(s => s.Address.City.Name))
|
||||
.ForMember(
|
||||
d => d.RegionUuid,
|
||||
opt => opt.MapFrom(s => s.Address.City.Region.Guid))
|
||||
.ForMember(
|
||||
d => d.RegionName,
|
||||
opt => opt.MapFrom(s => s.Address.City.Region.Name))
|
||||
.ForMember(
|
||||
d => d.CountryUuid,
|
||||
opt => opt.MapFrom(s => s.Address.City.Region.Country.Guid))
|
||||
.ForMember(
|
||||
d => d.CountryName,
|
||||
opt => opt.MapFrom(s => s.Address.City.Region.Country.Name));
|
||||
}
|
||||
}
|
29
src/Application/Routes/RouteDto.cs
Normal file
29
src/Application/Routes/RouteDto.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Mappings;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Application.Routes;
|
||||
|
||||
public sealed class RouteDto : IMapFrom<Route>
|
||||
{
|
||||
public Guid Uuid { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string VehicleType { get; set; }
|
||||
|
||||
public ICollection<RouteAddressDto> Addresses { get; set; }
|
||||
|
||||
public void Mapping(MappingProfile profile)
|
||||
{
|
||||
profile.CreateMap<Route, RouteDto>()
|
||||
.ForMember(
|
||||
d => d.Uuid,
|
||||
opt => opt.MapFrom(s => s.Guid))
|
||||
.ForMember(
|
||||
d => d.VehicleType,
|
||||
opt => opt.MapFrom(s => s.VehicleType.Name))
|
||||
.ForMember(
|
||||
d => d.Addresses,
|
||||
opt => opt.MapFrom(s => s.RouteAddresses));
|
||||
}
|
||||
}
|
10
src/Application/Routes/ViewModels/AddRouteViewModel.cs
Normal file
10
src/Application/Routes/ViewModels/AddRouteViewModel.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.ViewModels;
|
||||
|
||||
public sealed class AddRouteViewModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string VehicleType { get; set; }
|
||||
|
||||
public ICollection<RouteAddressViewModel> Addresses { get; set; }
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.ViewModels;
|
||||
|
||||
public sealed class GetRoutesPageFilterViewModel
|
||||
{
|
||||
public string? VehicleType { get; set; }
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.ViewModels;
|
||||
|
||||
public sealed class RouteAddressViewModel
|
||||
{
|
||||
public short Order { get; set; }
|
||||
|
||||
public Guid Uuid { get; set; }
|
||||
}
|
10
src/Application/Routes/ViewModels/UpdateRouteViewModel.cs
Normal file
10
src/Application/Routes/ViewModels/UpdateRouteViewModel.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace cuqmbr.TravelGuide.Application.Routes.ViewModels;
|
||||
|
||||
public sealed class UpdateRouteViewModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string VehicleType { get; set; }
|
||||
|
||||
public ICollection<RouteAddressViewModel> Addresses { get; set; }
|
||||
}
|
@ -19,5 +19,5 @@ public sealed class Address : EntityBase
|
||||
public City City { get; set; }
|
||||
|
||||
|
||||
// public ICollection<RouteAddress> AddressRoutes { get; set; }
|
||||
public ICollection<RouteAddress> AddressRoutes { get; set; }
|
||||
}
|
||||
|
@ -6,9 +6,8 @@ public sealed class Route : EntityBase
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
// public VehicleType VehicleType { get; set; }
|
||||
public VehicleType VehicleType { get; set; }
|
||||
|
||||
|
||||
public ICollection<RouteAddress> RouteAddresses { get; set; }
|
||||
}
|
||||
|
||||
|
198
src/HttpApi/Controllers/RoutesController.cs
Normal file
198
src/HttpApi/Controllers/RoutesController.cs
Normal file
@ -0,0 +1,198 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using cuqmbr.TravelGuide.Application.Common.Models;
|
||||
using cuqmbr.TravelGuide.Application.Common.ViewModels;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using cuqmbr.TravelGuide.Application.Routes;
|
||||
using cuqmbr.TravelGuide.Application.Routes.Commands.AddRoute;
|
||||
using cuqmbr.TravelGuide.Application.Routes.Queries.GetRoutesPage;
|
||||
using cuqmbr.TravelGuide.Application.Routes.Queries.GetRoute;
|
||||
using cuqmbr.TravelGuide.Application.Routes.Commands.UpdateRoute;
|
||||
using cuqmbr.TravelGuide.Application.Routes.Commands.DeleteRoute;
|
||||
using cuqmbr.TravelGuide.Application.Routes.ViewModels;
|
||||
using cuqmbr.TravelGuide.Application.Routes.Models;
|
||||
|
||||
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
|
||||
|
||||
[Route("routes")]
|
||||
public class RoutesController : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
[SwaggerOperation("Add a route")]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status201Created, "Object successfuly created",
|
||||
typeof(RouteDto))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status403Forbidden,
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "One or more addresses was not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
public async Task<ActionResult<RouteDto>> Add(
|
||||
[FromBody] AddRouteViewModel viewModel,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return StatusCode(
|
||||
StatusCodes.Status201Created,
|
||||
await Mediator.Send(
|
||||
new AddRouteCommand()
|
||||
{
|
||||
Name = viewModel.Name,
|
||||
VehicleType = VehicleType.FromName(viewModel.VehicleType),
|
||||
Addresses = viewModel.Addresses.Select(
|
||||
e => new RouteAddressModel()
|
||||
{
|
||||
Order = e.Order,
|
||||
Guid = e.Uuid
|
||||
|
||||
}).ToArray()
|
||||
},
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[SwaggerOperation("Get a list of all routes")]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status200OK, "Request successful",
|
||||
typeof(PaginatedList<RouteDto>))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status403Forbidden,
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
public async Task<PaginatedList<RouteDto>> GetPage(
|
||||
[FromQuery] PageQuery pageQuery, [FromQuery] SearchQuery searchQuery,
|
||||
[FromQuery] SortQuery sortQuery,
|
||||
[FromQuery] GetRoutesPageFilterViewModel filterQuery,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await Mediator.Send(
|
||||
new GetRoutesPageQuery()
|
||||
{
|
||||
PageNumber = pageQuery.PageNumber,
|
||||
PageSize = pageQuery.PageSize,
|
||||
Search = searchQuery.Search,
|
||||
Sort = sortQuery.Sort,
|
||||
VehicleType = VehicleType.FromName(filterQuery.VehicleType)
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet("{uuid:guid}")]
|
||||
[SwaggerOperation("Get a route by uuid")]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status200OK, "Request successful", typeof(RouteDto))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status403Forbidden,
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found", typeof(RouteDto))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
public async Task<RouteDto> Get(
|
||||
[FromRoute] Guid uuid,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await Mediator.Send(new GetRouteQuery() { Guid = uuid },
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[HttpPut("{uuid:guid}")]
|
||||
[SwaggerOperation("Update a route")]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest, "Object already exists",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status403Forbidden,
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found", typeof(RouteDto))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "One or more addresses was not found",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
public async Task<RouteDto> Update(
|
||||
[FromRoute] Guid uuid,
|
||||
[FromBody] UpdateRouteViewModel viewModel,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return await Mediator.Send(
|
||||
new UpdateRouteCommand()
|
||||
{
|
||||
Guid = uuid,
|
||||
Name = viewModel.Name,
|
||||
VehicleType = VehicleType.FromName(viewModel.VehicleType),
|
||||
Addresses = viewModel.Addresses.Select(
|
||||
e => new RouteAddressModel()
|
||||
{
|
||||
Order = e.Order,
|
||||
Guid = e.Uuid
|
||||
|
||||
}).ToArray()
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
[HttpDelete("{uuid:guid}")]
|
||||
[SwaggerOperation("Delete a route")]
|
||||
[SwaggerResponse(StatusCodes.Status204NoContent, "Request successful")]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status400BadRequest, "Input data validation error",
|
||||
typeof(HttpValidationProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status401Unauthorized, "Unauthorized to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status403Forbidden,
|
||||
"Not enough privileges to perform an action",
|
||||
typeof(ProblemDetails))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status404NotFound, "Object not found", typeof(RouteDto))]
|
||||
[SwaggerResponse(
|
||||
StatusCodes.Status500InternalServerError, "Internal server error",
|
||||
typeof(ProblemDetails))]
|
||||
public async Task<IActionResult> Delete(
|
||||
[FromRoute] Guid uuid,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await Mediator.Send(
|
||||
new DeleteRouteCommand() { Guid = uuid },
|
||||
cancellationToken);
|
||||
return StatusCode(StatusCodes.Status204NoContent);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Exceptions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace cuqmbr.TravelGuide.HttpApi.Middlewares;
|
||||
|
||||
@ -11,6 +11,8 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
|
||||
private readonly ILogger _logger;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
|
||||
|
||||
|
||||
public GlobalExceptionHandlerMiddleware(
|
||||
ILogger<GlobalExceptionHandlerMiddleware> logger,
|
||||
IStringLocalizer localizer)
|
||||
@ -75,7 +77,7 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
|
||||
|
||||
context.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
|
||||
await context.Response.WriteAsJsonAsync(new HttpValidationProblemDetails(ex.Errors)
|
||||
await context.Response.WriteAsJsonAsync(new HttpValidationProblemDetailsWithTraceId(ex.Errors)
|
||||
{
|
||||
Status = StatusCodes.Status400BadRequest,
|
||||
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1",
|
||||
@ -90,7 +92,7 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetails()
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetailsWithTraceId()
|
||||
{
|
||||
Status = StatusCodes.Status401Unauthorized,
|
||||
Type = "https://datatracker.ietf.org/doc/html/rfc7235#section-3.1",
|
||||
@ -105,7 +107,7 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetails()
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetailsWithTraceId()
|
||||
{
|
||||
Status = StatusCodes.Status400BadRequest,
|
||||
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1",
|
||||
@ -120,7 +122,7 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetails()
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetailsWithTraceId()
|
||||
{
|
||||
Status = StatusCodes.Status400BadRequest,
|
||||
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1",
|
||||
@ -135,7 +137,7 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetails()
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetailsWithTraceId()
|
||||
{
|
||||
Status = StatusCodes.Status400BadRequest,
|
||||
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1",
|
||||
@ -150,7 +152,7 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetails()
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetailsWithTraceId()
|
||||
{
|
||||
Status = StatusCodes.Status403Forbidden,
|
||||
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.3",
|
||||
@ -165,7 +167,7 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status400BadRequest;
|
||||
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetails()
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetailsWithTraceId()
|
||||
{
|
||||
Status = StatusCodes.Status400BadRequest,
|
||||
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1",
|
||||
@ -180,7 +182,7 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status404NotFound;
|
||||
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetails()
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetailsWithTraceId()
|
||||
{
|
||||
Status = StatusCodes.Status404NotFound,
|
||||
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4",
|
||||
@ -193,7 +195,7 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetails()
|
||||
await context.Response.WriteAsJsonAsync(new ProblemDetailsWithTraceId()
|
||||
{
|
||||
Status = StatusCodes.Status500InternalServerError,
|
||||
Type = "https://datatracker.ietf.org/doc/html/rfc7231#section-6.6.1",
|
||||
@ -202,9 +204,26 @@ public class GlobalExceptionHandlerMiddleware : IMiddleware
|
||||
});
|
||||
}
|
||||
|
||||
// class ProblemDetailsWithTraceId : ProblemDetails
|
||||
// {
|
||||
// public string TraceId { get; init; } = Activity.Current?.TraceId.ToString();
|
||||
// }
|
||||
}
|
||||
class ProblemDetailsWithTraceId : ProblemDetails
|
||||
{
|
||||
public ProblemDetailsWithTraceId()
|
||||
{
|
||||
Extensions = new Dictionary<string, object?>()
|
||||
{
|
||||
["traceId"] = Activity.Current.Id
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class HttpValidationProblemDetailsWithTraceId : HttpValidationProblemDetails
|
||||
{
|
||||
public HttpValidationProblemDetailsWithTraceId(
|
||||
IDictionary<string, string[]> errors) : base(errors)
|
||||
{
|
||||
Extensions = new Dictionary<string, object?>()
|
||||
{
|
||||
["traceId"] = Activity.Current.Id
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ public sealed class InMemoryUnitOfWork : UnitOfWork
|
||||
CountryRepository = new InMemoryCountryRepository(_dbContext);
|
||||
RegionRepository = new InMemoryRegionRepository(_dbContext);
|
||||
CityRepository = new InMemoryCityRepository(_dbContext);
|
||||
AddressRepository = new InMemoryAddressRepository(_dbContext);
|
||||
RouteRepository = new InMemoryRouteRepository(_dbContext);
|
||||
}
|
||||
|
||||
public CountryRepository CountryRepository { get; init; }
|
||||
@ -26,6 +28,8 @@ public sealed class InMemoryUnitOfWork : UnitOfWork
|
||||
|
||||
public AddressRepository AddressRepository { get; init; }
|
||||
|
||||
public RouteRepository RouteRepository { get; init; }
|
||||
|
||||
public int Save()
|
||||
{
|
||||
return _dbContext.SaveChanges();
|
||||
|
@ -0,0 +1,11 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.InMemory.Repositories;
|
||||
|
||||
public sealed class InMemoryRouteRepository :
|
||||
InMemoryBaseRepository<Route>, RouteRepository
|
||||
{
|
||||
public InMemoryRouteRepository(InMemoryDbContext dbContext)
|
||||
: base(dbContext) { }
|
||||
}
|
@ -18,7 +18,7 @@ public class AddressConfiguration : BaseConfiguration<Address>
|
||||
builder
|
||||
.ToTable(
|
||||
"addresses",
|
||||
b => b.HasCheckConstraint(
|
||||
a => a.HasCheckConstraint(
|
||||
"ck_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(a => a.VehicleType)
|
||||
|
@ -0,0 +1,81 @@
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Configurations;
|
||||
|
||||
public class RouteAddressConfiguration : BaseConfiguration<RouteAddress>
|
||||
{
|
||||
public override void Configure(EntityTypeBuilder<RouteAddress> builder)
|
||||
{
|
||||
builder
|
||||
.ToTable("route_addresses");
|
||||
|
||||
base.Configure(builder);
|
||||
|
||||
|
||||
builder
|
||||
.Property(ra => ra.Order)
|
||||
.HasColumnName("order")
|
||||
.HasColumnType("smallint")
|
||||
.IsRequired(true);
|
||||
|
||||
|
||||
builder
|
||||
.Property(ra => ra.AddressId)
|
||||
.HasColumnName("address_id")
|
||||
.HasColumnType("bigint")
|
||||
.IsRequired(true);
|
||||
|
||||
builder
|
||||
.HasOne(ra => ra.Address)
|
||||
.WithMany(a => a.AddressRoutes)
|
||||
.HasForeignKey(ra => ra.AddressId)
|
||||
.HasConstraintName(
|
||||
"fk_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(ra => ra.AddressId).Metadata.GetColumnName()}")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder
|
||||
.HasIndex(ra => ra.AddressId)
|
||||
.HasDatabaseName(
|
||||
"ix_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(ra => ra.AddressId).Metadata.GetColumnName()}");
|
||||
|
||||
|
||||
builder
|
||||
.Property(ra => ra.RouteId)
|
||||
.HasColumnName("route_id")
|
||||
.HasColumnType("bigint")
|
||||
.IsRequired(true);
|
||||
|
||||
builder
|
||||
.HasOne(ra => ra.Route)
|
||||
.WithMany(a => a.RouteAddresses)
|
||||
.HasForeignKey(ra => ra.RouteId)
|
||||
.HasConstraintName(
|
||||
"fk_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(ra => ra.RouteId).Metadata.GetColumnName()}")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder
|
||||
.HasIndex(ra => ra.RouteId)
|
||||
.HasDatabaseName(
|
||||
"ix_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(ra => ra.RouteId).Metadata.GetColumnName()}");
|
||||
|
||||
|
||||
builder
|
||||
.HasAlternateKey(ra => new { ra.AddressId, ra.RouteId, ra.Order })
|
||||
.HasName(
|
||||
"altk_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(ra => ra.AddressId).Metadata.GetColumnName()}_" +
|
||||
$"{builder.Property(ra => ra.RouteId).Metadata.GetColumnName()}_" +
|
||||
$"{builder.Property(ra => ra.Order).Metadata.GetColumnName()}");
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Configurations;
|
||||
|
||||
public class RouteConfiguration : BaseConfiguration<Route>
|
||||
{
|
||||
public override void Configure(EntityTypeBuilder<Route> builder)
|
||||
{
|
||||
builder
|
||||
.Property(r => r.VehicleType)
|
||||
.HasColumnName("vehicle_type")
|
||||
.HasColumnType("varchar(16)")
|
||||
.IsRequired(true);
|
||||
|
||||
builder
|
||||
.ToTable(
|
||||
"routes",
|
||||
r => r.HasCheckConstraint(
|
||||
"ck_" +
|
||||
$"{builder.Metadata.GetTableName()}_" +
|
||||
$"{builder.Property(a => a.VehicleType)
|
||||
.Metadata.GetColumnName()}",
|
||||
$"{builder.Property(a => a.VehicleType)
|
||||
.Metadata.GetColumnName()} IN ('{String
|
||||
.Join("', '", VehicleType.Enumerations
|
||||
.Values.Select(v => v.Name))}')"));
|
||||
|
||||
base.Configure(builder);
|
||||
|
||||
|
||||
builder
|
||||
.Property(r => r.Name)
|
||||
.HasColumnName("name")
|
||||
.HasColumnType("varchar(64)")
|
||||
.IsRequired(true);
|
||||
}
|
||||
}
|
400
src/Persistence/PostgreSql/Migrations/20250501112816_Add_Route_and_RouteAddresses.Designer.cs
generated
Normal file
400
src/Persistence/PostgreSql/Migrations/20250501112816_Add_Route_and_RouteAddresses.Designer.cs
generated
Normal file
@ -0,0 +1,400 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using cuqmbr.TravelGuide.Persistence.PostgreSql;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Persistence.PostgreSql.Migrations
|
||||
{
|
||||
[DbContext(typeof(PostgreSqlDbContext))]
|
||||
[Migration("20250501112816_Add_Route_and_RouteAddresses")]
|
||||
partial class Add_Route_and_RouteAddresses
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("application")
|
||||
.HasAnnotation("ProductVersion", "9.0.4")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.HasSequence("addresses_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("cities_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("countries_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("regions_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("route_addresses_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("routes_id_sequence");
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.addresses_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "addresses_id_sequence");
|
||||
|
||||
b.Property<long>("CityId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("city_id");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<double>("Latitude")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<double>("Longitude")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(128)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<string>("VehicleType")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(16)")
|
||||
.HasColumnName("vehicle_type");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_addresses");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_addresses_Guid");
|
||||
|
||||
b.HasIndex("CityId")
|
||||
.HasDatabaseName("ix_addresses_city_id");
|
||||
|
||||
b.HasIndex("Guid")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_addresses_uuid");
|
||||
|
||||
b.HasIndex("Id")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_addresses_id");
|
||||
|
||||
b.ToTable("addresses", "application", t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_addresses_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.cities_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "cities_id_sequence");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<long>("RegionId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("region_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_cities");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_cities_Guid");
|
||||
|
||||
b.HasIndex("Guid")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_cities_uuid");
|
||||
|
||||
b.HasIndex("Id")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_cities_id");
|
||||
|
||||
b.HasIndex("RegionId")
|
||||
.HasDatabaseName("ix_cities_region_id");
|
||||
|
||||
b.ToTable("cities", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Country", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.countries_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "countries_id_sequence");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_countries");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_countries_Guid");
|
||||
|
||||
b.HasIndex("Guid")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_countries_uuid");
|
||||
|
||||
b.HasIndex("Id")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_countries_id");
|
||||
|
||||
b.ToTable("countries", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.regions_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "regions_id_sequence");
|
||||
|
||||
b.Property<long>("CountryId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("country_id");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_regions");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_regions_Guid");
|
||||
|
||||
b.HasIndex("CountryId")
|
||||
.HasDatabaseName("ix_regions_country_id");
|
||||
|
||||
b.HasIndex("Guid")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_regions_uuid");
|
||||
|
||||
b.HasIndex("Id")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_regions_id");
|
||||
|
||||
b.ToTable("regions", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Route", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.routes_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "routes_id_sequence");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<string>("VehicleType")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(16)")
|
||||
.HasColumnName("vehicle_type");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_routes");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_routes_Guid");
|
||||
|
||||
b.HasIndex("Guid")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_routes_uuid");
|
||||
|
||||
b.HasIndex("Id")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_routes_id");
|
||||
|
||||
b.ToTable("routes", "application", t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_routes_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.route_addresses_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "route_addresses_id_sequence");
|
||||
|
||||
b.Property<long>("AddressId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("address_id");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<short>("Order")
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("order");
|
||||
|
||||
b.Property<long>("RouteId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("route_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_route_addresses");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_route_addresses_Guid");
|
||||
|
||||
b.HasAlternateKey("AddressId", "RouteId", "Order")
|
||||
.HasName("altk_route_addresses_address_id_route_id_order");
|
||||
|
||||
b.HasIndex("AddressId")
|
||||
.HasDatabaseName("ix_route_addresses_address_id");
|
||||
|
||||
b.HasIndex("Guid")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_route_addresses_uuid");
|
||||
|
||||
b.HasIndex("Id")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_route_addresses_id");
|
||||
|
||||
b.HasIndex("RouteId")
|
||||
.HasDatabaseName("ix_route_addresses_route_id");
|
||||
|
||||
b.ToTable("route_addresses", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.City", "City")
|
||||
.WithMany("Addresses")
|
||||
.HasForeignKey("CityId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_addresses_city_id");
|
||||
|
||||
b.Navigation("City");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Region", "Region")
|
||||
.WithMany("Cities")
|
||||
.HasForeignKey("RegionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_cities_region_id");
|
||||
|
||||
b.Navigation("Region");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Country", "Country")
|
||||
.WithMany("Regions")
|
||||
.HasForeignKey("CountryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_regions_country_id");
|
||||
|
||||
b.Navigation("Country");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Address", "Address")
|
||||
.WithMany("AddressRoutes")
|
||||
.HasForeignKey("AddressId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_route_addresses_address_id");
|
||||
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Route", "Route")
|
||||
.WithMany("RouteAddresses")
|
||||
.HasForeignKey("RouteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_route_addresses_route_id");
|
||||
|
||||
b.Navigation("Address");
|
||||
|
||||
b.Navigation("Route");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
|
||||
{
|
||||
b.Navigation("AddressRoutes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
|
||||
{
|
||||
b.Navigation("Addresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Country", b =>
|
||||
{
|
||||
b.Navigation("Regions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Region", b =>
|
||||
{
|
||||
b.Navigation("Cities");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Route", b =>
|
||||
{
|
||||
b.Navigation("RouteAddresses");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Persistence.PostgreSql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_Route_and_RouteAddresses : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateSequence(
|
||||
name: "route_addresses_id_sequence",
|
||||
schema: "application");
|
||||
|
||||
migrationBuilder.CreateSequence(
|
||||
name: "routes_id_sequence",
|
||||
schema: "application");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "routes",
|
||||
schema: "application",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<long>(type: "bigint", nullable: false, defaultValueSql: "nextval('application.routes_id_sequence')"),
|
||||
name = table.Column<string>(type: "varchar(64)", nullable: false),
|
||||
vehicle_type = table.Column<string>(type: "varchar(16)", nullable: false),
|
||||
uuid = table.Column<Guid>(type: "uuid", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_routes", x => x.id);
|
||||
table.UniqueConstraint("altk_routes_Guid", x => x.uuid);
|
||||
table.CheckConstraint("ck_routes_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "route_addresses",
|
||||
schema: "application",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<long>(type: "bigint", nullable: false, defaultValueSql: "nextval('application.route_addresses_id_sequence')"),
|
||||
order = table.Column<short>(type: "smallint", nullable: false),
|
||||
address_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
route_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
uuid = table.Column<Guid>(type: "uuid", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_route_addresses", x => x.id);
|
||||
table.UniqueConstraint("altk_route_addresses_address_id_route_id_order", x => new { x.address_id, x.route_id, x.order });
|
||||
table.UniqueConstraint("altk_route_addresses_Guid", x => x.uuid);
|
||||
table.ForeignKey(
|
||||
name: "fk_route_addresses_address_id",
|
||||
column: x => x.address_id,
|
||||
principalSchema: "application",
|
||||
principalTable: "addresses",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_route_addresses_route_id",
|
||||
column: x => x.route_id,
|
||||
principalSchema: "application",
|
||||
principalTable: "routes",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_route_addresses_address_id",
|
||||
schema: "application",
|
||||
table: "route_addresses",
|
||||
column: "address_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_route_addresses_id",
|
||||
schema: "application",
|
||||
table: "route_addresses",
|
||||
column: "id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_route_addresses_route_id",
|
||||
schema: "application",
|
||||
table: "route_addresses",
|
||||
column: "route_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_route_addresses_uuid",
|
||||
schema: "application",
|
||||
table: "route_addresses",
|
||||
column: "uuid",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_routes_id",
|
||||
schema: "application",
|
||||
table: "routes",
|
||||
column: "id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_routes_uuid",
|
||||
schema: "application",
|
||||
table: "routes",
|
||||
column: "uuid",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "route_addresses",
|
||||
schema: "application");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "routes",
|
||||
schema: "application");
|
||||
|
||||
migrationBuilder.DropSequence(
|
||||
name: "route_addresses_id_sequence",
|
||||
schema: "application");
|
||||
|
||||
migrationBuilder.DropSequence(
|
||||
name: "routes_id_sequence",
|
||||
schema: "application");
|
||||
}
|
||||
}
|
||||
}
|
@ -31,6 +31,10 @@ namespace Persistence.PostgreSql.Migrations
|
||||
|
||||
modelBuilder.HasSequence("regions_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("route_addresses_id_sequence");
|
||||
|
||||
modelBuilder.HasSequence("routes_id_sequence");
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
@ -210,6 +214,102 @@ namespace Persistence.PostgreSql.Migrations
|
||||
b.ToTable("regions", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Route", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.routes_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "routes_id_sequence");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<string>("VehicleType")
|
||||
.IsRequired()
|
||||
.HasColumnType("varchar(16)")
|
||||
.HasColumnName("vehicle_type");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_routes");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_routes_Guid");
|
||||
|
||||
b.HasIndex("Guid")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_routes_uuid");
|
||||
|
||||
b.HasIndex("Id")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_routes_id");
|
||||
|
||||
b.ToTable("routes", "application", t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_routes_vehicle_type", "vehicle_type IN ('bus', 'train', 'aircraft')");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id")
|
||||
.HasDefaultValueSql("nextval('application.route_addresses_id_sequence')");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseSequence(b.Property<long>("Id"), "route_addresses_id_sequence");
|
||||
|
||||
b.Property<long>("AddressId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("address_id");
|
||||
|
||||
b.Property<Guid>("Guid")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("uuid");
|
||||
|
||||
b.Property<short>("Order")
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("order");
|
||||
|
||||
b.Property<long>("RouteId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("route_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_route_addresses");
|
||||
|
||||
b.HasAlternateKey("Guid")
|
||||
.HasName("altk_route_addresses_Guid");
|
||||
|
||||
b.HasAlternateKey("AddressId", "RouteId", "Order")
|
||||
.HasName("altk_route_addresses_address_id_route_id_order");
|
||||
|
||||
b.HasIndex("AddressId")
|
||||
.HasDatabaseName("ix_route_addresses_address_id");
|
||||
|
||||
b.HasIndex("Guid")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_route_addresses_uuid");
|
||||
|
||||
b.HasIndex("Id")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_route_addresses_id");
|
||||
|
||||
b.HasIndex("RouteId")
|
||||
.HasDatabaseName("ix_route_addresses_route_id");
|
||||
|
||||
b.ToTable("route_addresses", "application");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.City", "City")
|
||||
@ -246,6 +346,32 @@ namespace Persistence.PostgreSql.Migrations
|
||||
b.Navigation("Country");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.RouteAddress", b =>
|
||||
{
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Address", "Address")
|
||||
.WithMany("AddressRoutes")
|
||||
.HasForeignKey("AddressId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_route_addresses_address_id");
|
||||
|
||||
b.HasOne("cuqmbr.TravelGuide.Domain.Entities.Route", "Route")
|
||||
.WithMany("RouteAddresses")
|
||||
.HasForeignKey("RouteId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_route_addresses_route_id");
|
||||
|
||||
b.Navigation("Address");
|
||||
|
||||
b.Navigation("Route");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Address", b =>
|
||||
{
|
||||
b.Navigation("AddressRoutes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.City", b =>
|
||||
{
|
||||
b.Navigation("Addresses");
|
||||
@ -260,6 +386,11 @@ namespace Persistence.PostgreSql.Migrations
|
||||
{
|
||||
b.Navigation("Cities");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("cuqmbr.TravelGuide.Domain.Entities.Route", b =>
|
||||
{
|
||||
b.Navigation("RouteAddresses");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
using System.Reflection;
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Microsoft.Extensions.Options;
|
||||
using cuqmbr.TravelGuide.Persistence.PostgreSql.TypeConverters;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.PostgreSql;
|
||||
|
||||
@ -41,12 +41,3 @@ public class PostgreSqlDbContext : DbContext
|
||||
.HaveConversion<VehicleTypeConverter>();
|
||||
}
|
||||
}
|
||||
|
||||
public class VehicleTypeConverter : ValueConverter<VehicleType, string>
|
||||
{
|
||||
public VehicleTypeConverter()
|
||||
: base(
|
||||
v => v.Name,
|
||||
v => VehicleType.FromName(v))
|
||||
{ }
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ public sealed class PostgreSqlUnitOfWork : UnitOfWork
|
||||
RegionRepository = new PostgreSqlRegionRepository(_dbContext);
|
||||
CityRepository = new PostgreSqlCityRepository(_dbContext);
|
||||
AddressRepository = new PostgreSqlAddressRepository(_dbContext);
|
||||
RouteRepository = new PostgreSqlRouteRepository(_dbContext);
|
||||
}
|
||||
|
||||
public CountryRepository CountryRepository { get; init; }
|
||||
@ -27,6 +28,8 @@ public sealed class PostgreSqlUnitOfWork : UnitOfWork
|
||||
|
||||
public AddressRepository AddressRepository { get; init; }
|
||||
|
||||
public RouteRepository RouteRepository { get; init; }
|
||||
|
||||
public int Save()
|
||||
{
|
||||
return _dbContext.SaveChanges();
|
||||
|
@ -0,0 +1,11 @@
|
||||
using cuqmbr.TravelGuide.Application.Common.Interfaces.Persistence.Repositories;
|
||||
using cuqmbr.TravelGuide.Domain.Entities;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.Repositories;
|
||||
|
||||
public sealed class PostgreSqlRouteRepository :
|
||||
PostgreSqlBaseRepository<Route>, RouteRepository
|
||||
{
|
||||
public PostgreSqlRouteRepository(PostgreSqlDbContext dbContext)
|
||||
: base(dbContext) { }
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
using cuqmbr.TravelGuide.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace cuqmbr.TravelGuide.Persistence.PostgreSql.TypeConverters;
|
||||
|
||||
public class VehicleTypeConverter : ValueConverter<VehicleType, string>
|
||||
{
|
||||
public VehicleTypeConverter()
|
||||
: base(
|
||||
v => v.Name,
|
||||
v => VehicleType.FromName(v))
|
||||
{ }
|
||||
}
|
Loading…
Reference in New Issue
Block a user