Skip to content

Commit

Permalink
Merge pull request #113
Browse files Browse the repository at this point in the history
Feat/available car
  • Loading branch information
kubapoke authored Dec 10, 2024
2 parents d0e7168 + b7e5a13 commit 697194b
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using CarRentalAPI.Models;

namespace CarRentalAPI.Abstractions.Repositories
{
public interface ICarRepository
{
public Task<List<Car>> GetCarsByIdAsync(List<int> ids, string? brand, string? model, string? location);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using CarRentalAPI.DTOs.Combinations;

namespace CarRentalAPI.Abstractions.Repositories
{
public interface IRentRepository
{
public Task<List<CarIdRentDatesDto>> GetChosenCarActiveRentDatesAsync(string? brand, string? model, string? location);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using CarRentalAPI.Abstractions;
using CarRentalAPI.DTOs.CarSearch;
using CarRentalAPI.Models;
using CarRentalAPI.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
Expand All @@ -12,15 +14,13 @@ namespace CarRentalAPI.Controllers
[ApiController]
public class OffersController : ControllerBase
{
private readonly CarRentalDbContext _context;
private readonly IPriceGenerator _priceGenerator;
private readonly OffersService _offersService;
private const string Conditions = "{}";
private const string CompanyName = "CarRental";

public OffersController(CarRentalDbContext context, IPriceGenerator priceGenerator)
public OffersController(OffersService offersService)
{
_context = context;
_priceGenerator = priceGenerator;
_offersService = offersService;
}

[HttpGet("offer-list")]
Expand All @@ -32,47 +32,28 @@ public async Task<IActionResult> OfferList(
[FromQuery] string? location,
[FromQuery] string? email)
{
if (startDate > endDate)
{
return BadRequest("Start date must be earlier than end date.");
}
if (startDate.Date < DateTime.Now.Date)
{
return BadRequest("Start date must be in the future.");
}
if (email == null)
{
return BadRequest("User email is required.");
}

var carList = await _context.Cars
.Include(car => car.Model)
.ThenInclude(model => model.Brand)
.ToListAsync();

var offers = carList
.Where(group =>
(group.IsActive)
&& (brand.IsNullOrEmpty() || group.Model.Brand.Name == brand)
&& (model.IsNullOrEmpty() || group.Model.Name == model)
&& (location.IsNullOrEmpty() || group.Location == location))
.Select(group => new
{
CarId = group.CarId,
Brand = group.Model.Brand.Name,
Model = group.Model.Year == null ? group.Model.Name : group.Model.Name + " " + group.Model.Year,
Price = _priceGenerator.GeneratePrice(group.Model.BasePrice, startDate, endDate),
Conditions = Conditions,
CompanyName = CompanyName,
Location = group.Location,
StartDate = startDate,
EndDate = endDate,
Email = email
})
.ToList();

return Ok(offers);
}
if (startDate > endDate)
{
return BadRequest("Start date must be earlier than end date.");
}
if (startDate.Date < DateTime.Now.Date)
{
return BadRequest("Start date must be in the future.");
}
if (email == null)
{
return BadRequest("User email is required.");
}

List<OfferForCarSearchDto> offers = await _offersService.GetNewOffers(brand, model, startDate, endDate, location, email, Conditions, CompanyName);

if (offers.Count == 0)
{
return BadRequest("We are sorry. There are no available cars of this type.");
}

return Ok(offers);
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace CarRentalAPI.DTOs.CarSearch
{
public class OfferForCarSearchDto
{
public int CarId { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public decimal Price { get; set; }
public string Conditions { get; set; }
public string CompanyName { get; set; }
public string Location { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string Email { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace CarRentalAPI.DTOs.Combinations
{
public class CarIdRentDatesDto
{
public int CarId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
}
6 changes: 6 additions & 0 deletions car-rental/backend/CarRentalAPI/CarRentalAPI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using CarRentalAPI.Abstractions.Repositories;
using CarRentalAPI.Repositories;

var builder = WebApplication.CreateBuilder(args);

Expand All @@ -29,6 +31,10 @@
builder.Services.AddScoped<PasswordHasher>();
builder.Services.AddScoped<SessionTokenManager>();
builder.Services.AddScoped<IStorageManager, AzureBlobStorageManager>();
builder.Services.AddScoped<IRentRepository, RentRepository>();
builder.Services.AddScoped<ICarRepository, CarRepository>();
builder.Services.AddScoped<AvailabilityChecker>();
builder.Services.AddScoped<OffersService>();

builder.Services.AddAuthentication(options => // that is instruction, how to check bearer token
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using CarRentalAPI.Abstractions.Repositories;
using CarRentalAPI.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;

namespace CarRentalAPI.Repositories
{
public class CarRepository : ICarRepository
{
private readonly CarRentalDbContext _context;

public CarRepository(CarRentalDbContext context)
{
_context = context;
}

public async Task<List<Car>> GetCarsByIdAsync(List<int> ids, string? brand, string? model, string? location)
{
var cars = await _context.Cars
.Where(c => ((!ids.Contains(c.CarId))) &&
(brand.IsNullOrEmpty() || c.Model.Brand.Name == brand) &&
(model.IsNullOrEmpty() || c.Model.Name == model) &&
(location.IsNullOrEmpty() || c.Location == location))
.Include(c => c.Model)
.ThenInclude(m => m.Brand).ToListAsync();
return cars;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using CarRentalAPI.Abstractions.Repositories;
using CarRentalAPI.DTOs.Combinations;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;

namespace CarRentalAPI.Repositories
{
public class RentRepository : IRentRepository
{
private readonly CarRentalDbContext _context;

public RentRepository(CarRentalDbContext context)
{
_context = context;
}

public async Task<List<CarIdRentDatesDto>> GetChosenCarActiveRentDatesAsync(string? brand, string? model, string? location)
{
try
{
var query = from r in _context.Rents
join c in _context.Cars on r.CarId equals c.CarId
where
(brand.IsNullOrEmpty() || c.Model.Brand.Name == brand) &&
(model.IsNullOrEmpty() || c.Model.Name == model) &&
(location.IsNullOrEmpty() || c.Location == location) &&
(r.RentEnd > DateTime.Today)
select new CarIdRentDatesDto { CarId = c.CarId, StartDate = r.RentStart, EndDate = r.RentEnd.Value };

Check warning on line 28 in car-rental/backend/CarRentalAPI/CarRentalAPI/Repositories/RentRepository.cs

View workflow job for this annotation

GitHub Actions / build

Nullable value type may be null.
return await query.ToListAsync();
}
catch (InvalidOperationException ex)
{
Console.WriteLine("With high probability rent without RentEnd appeared in the database for some reason\n");
throw new InvalidOperationException("Error while fetching rent data.", ex);

}

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using CarRentalAPI.DTOs.Combinations;

namespace CarRentalAPI.Services
{
public class AvailabilityChecker
{
public List<int> CheckForNotAvailableCars(List<CarIdRentDatesDto> pairs, DateTime startDate, DateTime endDate)
{
List<int> notAvailableCarIds = new List<int>();
foreach (var pair in pairs)
{
if (DoIntervalsCollide(pair.StartDate, pair.EndDate, startDate, endDate)) notAvailableCarIds.Add(pair.CarId);
}
return notAvailableCarIds;
}

private bool DoIntervalsCollide(DateTime start1, DateTime end1, DateTime start2, DateTime end2)
{
// Check if interval 2 collides with interval 1
if ((end2 <= end1) && (end2 >= start1)) return true;
if ((start2 >= start1) && (start2 <= end1)) return true;
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using CarRentalAPI.Abstractions;
using CarRentalAPI.Abstractions.Repositories;
using CarRentalAPI.DTOs.CarSearch;
using CarRentalAPI.DTOs.Combinations;
using CarRentalAPI.Models;

namespace CarRentalAPI.Services
{
public class OffersService
{
private readonly IRentRepository _rentRepository;
private readonly ICarRepository _carRepository;
private readonly AvailabilityChecker _availabilityChecker;
private readonly IPriceGenerator _priceGenerator;

public OffersService(IRentRepository rentRepository, ICarRepository carRepository, AvailabilityChecker availabilityChecker, IPriceGenerator priceGenerator)
{
_rentRepository = rentRepository;
_carRepository = carRepository;
_availabilityChecker = availabilityChecker;
_priceGenerator = priceGenerator;
}

public async Task<List<OfferForCarSearchDto>> GetNewOffers(string? brand, string? model, DateTime startDate, DateTime endDate, string? location, string email, string conditions, string companyName)
{
List<CarIdRentDatesDto> pairs = await _rentRepository.GetChosenCarActiveRentDatesAsync(brand, model, location);
List<int> notAvailableCarIds = _availabilityChecker.CheckForNotAvailableCars(pairs, startDate, endDate);
List<Car> availableCars = await _carRepository.GetCarsByIdAsync(notAvailableCarIds, brand, model, location);

List<OfferForCarSearchDto> newOffers = new List<OfferForCarSearchDto>();
foreach (var car in availableCars)
{
newOffers.Add(new OfferForCarSearchDto
{
CarId = car.CarId,
Brand = car.Model.Brand.Name,
Model = car.Model.Year == null ? car.Model.Name : car.Model.Name + " " + car.Model.Year,
Price = _priceGenerator.GeneratePrice(car.Model.BasePrice, startDate, endDate),
Conditions = conditions,
CompanyName = companyName,
Location = car.Location,
StartDate = startDate,
EndDate = endDate,
Email = email
});
}

return newOffers;
}
}
}

0 comments on commit 697194b

Please sign in to comment.