using System.Dynamic; using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Server.Data; using Server.Helpers; using SharedModels.DataTransferObjects; using SharedModels.QueryParameters; using SharedModels.QueryParameters.Objects; using Route = Server.Models.Route; namespace Server.Services; public class RouteManagementService : IRouteManagementService { private readonly ApplicationDbContext _dbContext; private readonly IMapper _mapper; private readonly ISortHelper _routeSortHelper; private readonly IDataShaper _routeDataShaper; private readonly IDataShaper _routeWithAddressesDataShaper; private readonly IPager _pager; public RouteManagementService(ApplicationDbContext dbContext, IMapper mapper, ISortHelper routeSortHelper, IDataShaper routeDataShaper, IDataShaper routeWithAddressesDataShaper, IPager pager) { _dbContext = dbContext; _mapper = mapper; _routeSortHelper = routeSortHelper; _routeDataShaper = routeDataShaper; _routeWithAddressesDataShaper = routeWithAddressesDataShaper; _pager = pager; } public async Task<(bool isSucceed, string message, RouteDto route)> AddRoute(CreateRouteDto createRouteDto) { var route = _mapper.Map(createRouteDto); await _dbContext.Routes.AddAsync(route); await _dbContext.SaveChangesAsync(); return (true, String.Empty, _mapper.Map(route)); } public async Task<(bool isSucceed, IActionResult? actionResult, RouteWithAddressesDto route)> AddRouteWithAddresses(CreateRouteWithAddressesDto createRouteWithAddressesDto) { var route = _mapper.Map(createRouteWithAddressesDto); foreach (var routeAddress in route.RouteAddresses) { var dbAddress = await _dbContext.Addresses .FirstOrDefaultAsync(a => a.Id == routeAddress.AddressId); if (dbAddress == null) { return (false, new BadRequestObjectResult($"Address with Id = {routeAddress.AddressId} doesn't exist"), null!); } routeAddress.Address = dbAddress; } await _dbContext.Routes.AddAsync(route); await _dbContext.SaveChangesAsync(); return (true, null, _mapper.Map(route)); } public async Task<(bool isSucceed, string message, IEnumerable routes, PagingMetadata pagingMetadata)> GetRoutes(RouteParameters parameters) { var dbRoutes = _dbContext.Routes .AsQueryable(); SearchByAllRouteFields(ref dbRoutes, parameters.Search); FilterByRouteType(ref dbRoutes, parameters.Type); var routeDtos = _mapper.ProjectTo(dbRoutes); var shapedData = _routeDataShaper.ShapeData(routeDtos, parameters.Fields).AsQueryable(); try { shapedData = _routeSortHelper.ApplySort(shapedData, parameters.Sort); // By calling Any() we will check if LINQ to Entities Query will be // executed. If not it will throw an InvalidOperationException exception var isExecuted = dbRoutes.Any(); } catch (Exception e) { return (false, "Invalid sorting string", null, null)!; } var pagingMetadata = _pager.ApplyPaging(ref shapedData, parameters.PageNumber, parameters.PageSize); return (true, "", shapedData, pagingMetadata); void SearchByAllRouteFields(ref IQueryable route, string? search) { if (!route.Any() || String.IsNullOrWhiteSpace(search)) { return; } route = route.Where(c => c.Type.ToLower().Contains(search.ToLower())); } void FilterByRouteType(ref IQueryable routes, string? type) { if (!routes.Any() || String.IsNullOrWhiteSpace(type)) { return; } routes = routes.Where(r => r.Type == type); } } public async Task<(bool isSucceed, IActionResult? actionResult, IEnumerable routes, PagingMetadata pagingMetadata)> GetRoutesWithAddresses(RouteWithAddressesParameters parameters) { var dbRoutes = _dbContext.Routes .Include(r => r.RouteAddresses.OrderBy(ra => ra.Order)) .ThenInclude(ra => ra.Address).ThenInclude(a => a.City) .ThenInclude(c => c.State).ThenInclude(s => s.Country) .AsQueryable(); SearchByAllRouteFields(ref dbRoutes, parameters.Search); FilterByRouteType(ref dbRoutes, parameters.Type); FilterByFromAddressName(ref dbRoutes, parameters.FromAddressName); FilterByToAddressName(ref dbRoutes, parameters.ToAddressName); var routeDtos = _mapper.ProjectTo(dbRoutes); var shapedData = _routeWithAddressesDataShaper.ShapeData(routeDtos, parameters.Fields).AsQueryable(); try { shapedData = _routeSortHelper.ApplySort(shapedData, parameters.Sort); } catch (Exception e) { return (false, new BadRequestObjectResult("Invalid sorting string"), null, null)!; } var pagingMetadata = _pager.ApplyPaging(ref shapedData, parameters.PageNumber, parameters.PageSize); return (true, null, shapedData, pagingMetadata); void SearchByAllRouteFields(ref IQueryable route, string? search) { if (!route.Any() || String.IsNullOrWhiteSpace(search)) { return; } // TODO Optimize (remove client evaluation) route = route.ToArray().Where(r => r.Type.ToLower().Contains(search.ToLower()) || r.RouteAddresses.OrderBy(ra => ra.Order).First().Address .GetFullName().ToLower().Contains(search.ToLower()) || r.RouteAddresses.OrderBy(ra => ra.Order).Last().Address .GetFullName().ToLower().Contains(search.ToLower())) .AsQueryable(); } void FilterByRouteType(ref IQueryable routes, string? type) { if (!routes.Any() || String.IsNullOrWhiteSpace(type)) { return; } routes = routes.Where(r => r.Type.ToLower().Contains(type.ToLower())); } void FilterByFromAddressName(ref IQueryable routes, string? addressName) { if (!routes.Any() || String.IsNullOrWhiteSpace(addressName)) { return; } // TODO Optimize (remove client evaluation) routes = routes.ToArray().Where(r => r.RouteAddresses.First().Address .GetFullName().ToLower().Contains(addressName.ToLower())) .AsQueryable(); } void FilterByToAddressName(ref IQueryable routes, string? addressName) { if (!routes.Any() || String.IsNullOrWhiteSpace(addressName)) { return; } // TODO Optimize (remove client evaluation) routes = routes.ToArray().Where(r => r.RouteAddresses.Last().Address. GetFullName().ToLower().Contains(addressName.ToLower())) .AsQueryable(); } } public async Task<(bool isSucceed, string message, ExpandoObject route)> GetRoute(int id, string? fields) { var dbRoute = await _dbContext.Routes.Where(r => r.Id == id) .FirstOrDefaultAsync(); if (dbRoute == null) { return (false, $"Route doesn't exist", null)!; } if (String.IsNullOrWhiteSpace(fields)) { fields = RouteParameters.DefaultFields; } var routeDto = _mapper.Map(dbRoute); var shapedRouteData = _routeDataShaper.ShapeData(routeDto, fields); return (true, "", shapedRouteData); } public async Task<(bool isSucceed, string message, ExpandoObject route)> GetRouteWithAddresses(int id, string? fields) { var dbRoute = await _dbContext.Routes.Where(r => r.Id == id) .Include(r => r.RouteAddresses).ThenInclude(ra => ra.Address) .ThenInclude(a => a.City).ThenInclude(c => c.State) .ThenInclude(s => s.Country).FirstOrDefaultAsync(); if (dbRoute == null) { return (false, $"Route doesn't exist", null)!; } if (String.IsNullOrWhiteSpace(fields)) { fields = RouteWithAddressesParameters.DefaultFields; } var routeDto = _mapper.Map(dbRoute); var shapedRouteData = _routeDataShaper.ShapeData(routeDto, fields); return (true, "", shapedRouteData); } public async Task<(bool isSucceed, string message, UpdateRouteDto route)> UpdateRoute(UpdateRouteDto updateRouteDto) { var route = _mapper.Map(updateRouteDto); _dbContext.Entry(route).State = EntityState.Modified; try { await _dbContext.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!await IsRouteExists(updateRouteDto.Id)) { return (false, $"Route with id:{updateRouteDto.Id} doesn't exist", null)!; } throw; } var dbRoute = await _dbContext.Routes.FirstOrDefaultAsync(r => r.Id == route.Id); return (true, String.Empty, _mapper.Map(dbRoute)); } public async Task<(bool isSucceed, string message)> DeleteRoute(int id) { var dbRoute = await _dbContext.Routes.FirstOrDefaultAsync(r => r.Id == id); if (dbRoute == null) { return (false, $"Route with id:{id} doesn't exist"); } _dbContext.Routes.Remove(dbRoute); await _dbContext.SaveChangesAsync(); return (true, String.Empty); } public async Task IsRouteExists(int id) { return await _dbContext.Routes.AnyAsync(r => r.Id == id); } }