338 lines
12 KiB
C#
338 lines
12 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Solicitors.Core.Data;
|
|
using Solicitors.Core.Models;
|
|
using Solicitors.Core.Models.Imports;
|
|
using Solicitors.Data.RepositorySetup;
|
|
|
|
namespace Solicitors.Data;
|
|
|
|
internal class SolicitorsRepository(IRepoSetupService setup)
|
|
: DbContext, ISolicitorRepository, IReadOnlyCitiesRepository, IRatingsProviderRepository
|
|
{
|
|
public DbSet<Solicitor> Solicitors { get; set; }
|
|
public DbSet<SolicitorRating> SolicitorRatings { get; set; }
|
|
public DbSet<SolicitorLocation> SolicitorLocations { get; set; }
|
|
public DbSet<SolicitorLocationRating> SolicitorLocationRatings { get; set; }
|
|
public DbSet<City> Cities { get; set; }
|
|
|
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
|
=> setup.OnConfiguring(optionsBuilder);
|
|
|
|
public Task EnsureCreatedAsync(CancellationToken cancellationToken)
|
|
=> Database.EnsureCreatedAsync(cancellationToken);
|
|
|
|
public IAsyncEnumerable<Solicitor> GetAllSolicitorsAsync()
|
|
=> Solicitors
|
|
.Include(solicitor => solicitor.Cities)
|
|
.Include(solicitor => solicitor.Locations)
|
|
.ThenInclude(location => location.LocationRatings)
|
|
.Include(solicitor => solicitor.Ratings)
|
|
.AsAsyncEnumerable();
|
|
|
|
public Task<Solicitor?> GetSolicitorByIdAsync(Guid id, CancellationToken cancellationToken = default)
|
|
=> Solicitors
|
|
.Include(solicitor => solicitor.Cities)
|
|
.Include(solicitor => solicitor.Locations)
|
|
.ThenInclude(location => location.LocationRatings)
|
|
.Include(solicitor => solicitor.Ratings)
|
|
.FirstOrDefaultAsync(x => x.SolicitorId == id, cancellationToken);
|
|
|
|
public IAsyncEnumerable<string> GetCitiesAsync()
|
|
{
|
|
return Cities
|
|
.Select(city => city.Name)
|
|
.AsAsyncEnumerable();
|
|
}
|
|
|
|
public IAsyncEnumerable<string> GetAllRatingsProvidersAsync()
|
|
{
|
|
return SolicitorRatings
|
|
.Select(rating => rating.Provider)
|
|
.Distinct()
|
|
.OrderBy(x => x == "Solicitors.com" ? 0 : 1)
|
|
.ThenBy(x => x)
|
|
.AsAsyncEnumerable();
|
|
}
|
|
|
|
public async Task AddOrUpdateSolicitorAsync(SolicitorData solicitor, CancellationToken cancellationToken)
|
|
{
|
|
var matchingSolicitor = await Solicitors
|
|
.Include(existing => existing.Cities)
|
|
.Include(existing => existing.Locations)
|
|
.ThenInclude(location => location.LocationRatings)
|
|
.Include(existing => existing.Ratings)
|
|
.FirstOrDefaultAsync(
|
|
item => item.Name == solicitor.Name,
|
|
cancellationToken: cancellationToken);
|
|
|
|
if (matchingSolicitor is null)
|
|
await AddNewSolicitor(solicitor, cancellationToken);
|
|
else
|
|
UpdateExistingSolicitor(solicitor, matchingSolicitor);
|
|
|
|
try
|
|
{
|
|
await SaveChangesAsync(cancellationToken);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine(e);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public Task RemoveStaleEntriesAsync(TimeSpan staleAge, CancellationToken cancellationToken)
|
|
{
|
|
var staleTime = DateTime.UtcNow - staleAge;
|
|
|
|
Solicitors.RemoveRange(Solicitors.Where(x => x.LastModified < staleTime));
|
|
SolicitorLocations.RemoveRange(SolicitorLocations.Where(x => x.LastModified < staleTime));
|
|
SolicitorRatings.RemoveRange(SolicitorRatings.Where(x => x.LastModified < staleTime));
|
|
SolicitorLocationRatings.RemoveRange(SolicitorLocationRatings.Where(x => x.LastModified < staleTime));
|
|
Cities.RemoveRange(Cities.Where(x => x.LastModified < staleTime));
|
|
|
|
return SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
private async Task AddNewSolicitor(
|
|
SolicitorData solicitor,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var newSol = new Solicitor
|
|
{
|
|
Name = solicitor.Name,
|
|
RelativeUrl = solicitor.UrlPath
|
|
};
|
|
UpdateSolicitorRootData(newSol, solicitor);
|
|
Solicitors.Add(newSol);
|
|
|
|
var cityTasks = solicitor.Cities
|
|
.Distinct()
|
|
.Select(async city =>
|
|
{
|
|
var matchingCity = await GetCityByNameAsync(city, cancellationToken);
|
|
|
|
if (matchingCity is null)
|
|
AddNewCityToSolicitor(city, newSol);
|
|
else
|
|
UpdateExistingCity(matchingCity, newSol);
|
|
});
|
|
|
|
await Task.WhenAll(cityTasks);
|
|
|
|
foreach (var rating in solicitor.Ratings)
|
|
AddNewRatingToSolicitor(rating, newSol);
|
|
|
|
foreach (var location in solicitor.Offices)
|
|
AddNewLocationToSolicitor(location, newSol);
|
|
}
|
|
|
|
private void UpdateSolicitorRootData(Solicitor solicitor, SolicitorData data)
|
|
{
|
|
solicitor.Name = data.Name;
|
|
solicitor.RelativeUrl = data.UrlPath;
|
|
solicitor.Phone = data.Phone;
|
|
solicitor.Email = data.Email;
|
|
solicitor.Website = data.Website;
|
|
solicitor.ShortDescription = data.ShortDescription;
|
|
solicitor.LastModified = DateTime.UtcNow;
|
|
}
|
|
|
|
private void AddNewCityToSolicitor(string cityName, Solicitor solicitor)
|
|
{
|
|
var newCity = new City
|
|
{
|
|
Name = cityName
|
|
};
|
|
Cities.Add(newCity);
|
|
UpdateExistingCity(newCity, solicitor);
|
|
}
|
|
|
|
private void AddNewRatingToSolicitor(Rating rating, Solicitor solicitor)
|
|
{
|
|
var newRating = new SolicitorRating
|
|
{
|
|
Value = rating.Value,
|
|
Maximum = rating.MaxValue,
|
|
Provider = rating.RatingProvider,
|
|
Solicitor = solicitor,
|
|
LastModified = DateTime.UtcNow
|
|
};
|
|
SolicitorRatings.Add(newRating);
|
|
solicitor.Ratings.Add(newRating);
|
|
}
|
|
|
|
private void AddNewLocationToSolicitor(Location location, Solicitor solicitor)
|
|
{
|
|
var newLocation = new SolicitorLocation
|
|
{
|
|
Address = location.Address,
|
|
Phone = location.Phone,
|
|
Solicitor = solicitor,
|
|
LastModified = DateTime.UtcNow
|
|
};
|
|
SolicitorLocations.Add(newLocation);
|
|
solicitor.Locations.Add(newLocation);
|
|
|
|
foreach (var rating in location.LocationRatings)
|
|
AddNewRatingToLocation(rating, newLocation);
|
|
}
|
|
|
|
private void AddNewRatingToLocation(Rating rating, SolicitorLocation location)
|
|
{
|
|
var newLocRating = new SolicitorLocationRating
|
|
{
|
|
Value = rating.Value,
|
|
Maximum = rating.MaxValue,
|
|
Provider = rating.RatingProvider,
|
|
SolicitorLocation = location,
|
|
LastModified = DateTime.UtcNow
|
|
};
|
|
SolicitorLocationRatings.Add(newLocRating);
|
|
location.LocationRatings.Add(newLocRating);
|
|
}
|
|
|
|
private void UpdateExistingSolicitor(
|
|
SolicitorData solicitor,
|
|
Solicitor matchingSolicitor)
|
|
{
|
|
UpdateSolicitorRootData(matchingSolicitor, solicitor);
|
|
UpdateExistingCities(solicitor, matchingSolicitor);
|
|
UpdateExistingRatings(solicitor, matchingSolicitor);
|
|
UpdateExistingLocations(solicitor, matchingSolicitor);
|
|
}
|
|
|
|
private void UpdateExistingCities(
|
|
SolicitorData solicitor,
|
|
Solicitor matchingSolicitor)
|
|
{
|
|
var lostCities = matchingSolicitor.Cities
|
|
.Where(city => solicitor.Cities.All(cityName => cityName != city.Name))
|
|
.ToArray();
|
|
|
|
foreach (var city in lostCities)
|
|
{
|
|
matchingSolicitor.Cities.Remove(city);
|
|
city.Solicitors.Remove(matchingSolicitor);
|
|
}
|
|
foreach (var cityName in solicitor.Cities.Distinct())
|
|
{
|
|
var matchingCity = matchingSolicitor.Cities
|
|
.FirstOrDefault(city => cityName == city.Name);
|
|
|
|
if (matchingCity is null)
|
|
AddNewCityToSolicitor(cityName, matchingSolicitor);
|
|
else
|
|
UpdateExistingCity(matchingCity, matchingSolicitor);
|
|
}
|
|
}
|
|
|
|
private void UpdateExistingRatings(
|
|
SolicitorData solicitor,
|
|
Solicitor matchingSolicitor)
|
|
{
|
|
var lostRatings = matchingSolicitor.Ratings
|
|
.Where(solicitorRating => solicitor.Ratings.All(rating => rating.RatingProvider != solicitorRating.Provider))
|
|
.ToArray();
|
|
|
|
foreach (var rating in lostRatings)
|
|
{
|
|
matchingSolicitor.Ratings.Remove(rating);
|
|
SolicitorRatings.Remove(rating);
|
|
}
|
|
|
|
foreach (var rating in solicitor.Ratings)
|
|
{
|
|
var matchingRating = matchingSolicitor.Ratings
|
|
.FirstOrDefault(solicitorRating => rating.RatingProvider == solicitorRating.Provider);
|
|
|
|
if (matchingRating is null)
|
|
AddNewRatingToSolicitor(rating, matchingSolicitor);
|
|
else
|
|
UpdateExistingRating(rating, matchingRating);
|
|
}
|
|
}
|
|
|
|
private void UpdateExistingRating(Rating rating, SolicitorRating existingRating)
|
|
{
|
|
existingRating.Value = rating.Value;
|
|
existingRating.Maximum = rating.MaxValue;
|
|
existingRating.LastModified = DateTime.UtcNow;
|
|
}
|
|
|
|
private void UpdateExistingLocations(
|
|
SolicitorData solicitor,
|
|
Solicitor matchingSolicitor)
|
|
{
|
|
var lostLocations = matchingSolicitor.Locations
|
|
.Where(location => solicitor.Offices.All(office => office.Address != location.Address))
|
|
.ToArray();
|
|
|
|
foreach (var location in lostLocations)
|
|
{
|
|
matchingSolicitor.Locations.Remove(location);
|
|
SolicitorLocations.Remove(location);
|
|
}
|
|
|
|
foreach (var office in solicitor.Offices)
|
|
{
|
|
var location = matchingSolicitor.Locations
|
|
.FirstOrDefault(location => office.Address == location.Address);
|
|
|
|
if (location is null)
|
|
AddNewLocationToSolicitor(office, matchingSolicitor);
|
|
else
|
|
UpdateExistingLocation(office, location);
|
|
}
|
|
}
|
|
|
|
private void UpdateExistingLocation(Location location, SolicitorLocation existingLocation)
|
|
{
|
|
existingLocation.Phone = location.Phone;
|
|
existingLocation.LastModified = DateTime.UtcNow;
|
|
|
|
var lostRatings = existingLocation.LocationRatings
|
|
.Where(locationRating => location.LocationRatings.All(rating => locationRating.Provider != rating.RatingProvider))
|
|
.ToArray();
|
|
|
|
foreach (var rating in lostRatings)
|
|
{
|
|
existingLocation.LocationRatings.Remove(rating);
|
|
SolicitorLocationRatings.Remove(rating);
|
|
}
|
|
|
|
foreach (var rating in location.LocationRatings)
|
|
{
|
|
var existingRating = existingLocation.LocationRatings
|
|
.FirstOrDefault(locationRating => locationRating.Provider == rating.RatingProvider);
|
|
|
|
if (existingRating is null)
|
|
AddNewRatingToLocation(rating, existingLocation);
|
|
else
|
|
UpdateExistingLocationRating(rating, existingRating);
|
|
}
|
|
}
|
|
|
|
private void UpdateExistingLocationRating(Rating rating, SolicitorLocationRating existingRating)
|
|
{
|
|
existingRating.Value = rating.Value;
|
|
existingRating.Maximum = rating.MaxValue;
|
|
existingRating.LastModified = DateTime.UtcNow;
|
|
}
|
|
|
|
private void UpdateExistingCity(City city, Solicitor solicitor)
|
|
{
|
|
city.Solicitors.Add(solicitor);
|
|
solicitor.Cities.Add(city);
|
|
city.LastModified = DateTime.UtcNow;
|
|
}
|
|
|
|
private Task<City?> GetCityByNameAsync(string cityName, CancellationToken cancellationToken)
|
|
{
|
|
return Cities
|
|
.Include(city => city.Solicitors)
|
|
.FirstOrDefaultAsync(
|
|
city => city.Name == cityName,
|
|
cancellationToken: cancellationToken);
|
|
}
|
|
} |