diff --git a/backend/Dockerfile b/backend/Dockerfile
index f0dfe09..4fa5927 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -13,4 +13,4 @@ FROM mcr.microsoft.com/dotnet/aspnet:10.0
VOLUME /data
WORKDIR /App
COPY --from=build /App/out .
-ENTRYPOINT ["dotnet", "SolicitorsApi.dll"]
\ No newline at end of file
+ENTRYPOINT ["dotnet", "Solicitors.Api.dll"]
\ No newline at end of file
diff --git a/backend/SolicitorsApi.slnx b/backend/SolicitorsApi.slnx
index f7accb8..4d0780e 100644
--- a/backend/SolicitorsApi.slnx
+++ b/backend/SolicitorsApi.slnx
@@ -1,4 +1,7 @@
+
+
+
@@ -8,6 +11,4 @@
-
-
diff --git a/backend/SolicitorsApi/Program.cs b/backend/SolicitorsApi/Program.cs
deleted file mode 100644
index c03d35d..0000000
--- a/backend/SolicitorsApi/Program.cs
+++ /dev/null
@@ -1,176 +0,0 @@
-using System.ComponentModel;
-using Microsoft.AspNetCore.Mvc;
-using Solicitors.CacheBuild;
-using Solicitors.Core;
-using Solicitors.Core.Misc;
-using Solicitors.Core.Models;
-using Solicitors.Data;
-using Solicitors.Data.RepositorySetup.Sqlite;
-using Solicitors.HtmlParsing;
-
-
-var builder = WebApplication.CreateBuilder(args);
-
-builder.Services.AddOpenApi();
-builder.Services.AddCore();
-builder.Services.AddData(new SqliteDbSetupOptions("demo"));
-builder.Services.AddCacheBuild();
-builder.Services.AddHtmlParsing();
-builder.Services.AddHttpClient();
-builder.Services.AddCors(options =>
-{
- options.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
-});
-
-var app = builder.Build();
-
-// Configure the HTTP request pipeline.
-if (app.Environment.IsDevelopment())
-{
- app.MapOpenApi();
-}
-
-app.UseHttpsRedirection();
-app.UseCors();
-
-app.MapGet(
- "/conveyancors",
- (
- [FromServices] IReadOnlySolicitorService solicitorService,
- CancellationToken cancellationToken,
- [FromQuery] uint pageNumber = 1,
- [FromQuery] uint pageSize = 20,
- [FromQuery] string? nameFilter = null,
- [FromQuery] decimal minRating = 0,
- [FromQuery] string[]? cities = null,
- [FromQuery] string ratingsProvider = "Solicitors.com",
- [FromQuery] string? orderingType = null) =>
- {
- IFilter? filter = null;
-
- if (!string.IsNullOrEmpty(nameFilter))
- filter = new WrapperFilter(filter, solicitor => solicitor.Name.Contains(nameFilter));
-
- if (cities is not null && cities.Length > 0)
- filter = new WrapperFilter(filter, solicitor => cities.Any(city => solicitor.Cities.Any(solCity => solCity.Name == city)));
-
- if (minRating > 0)
- filter = new WrapperFilter(
- filter,
- solicitor => solicitor.Ratings.Any(rating => (rating.Provider == ratingsProvider) && (rating.Value / rating.Maximum >= minRating / 5.0m)));
-
- IComparer? ordering = orderingType switch
- {
- "rating-asc" => new RatingComparer(ratingsProvider, true),
- "rating-desc" => new RatingComparer(ratingsProvider, false),
- "alphabet-asc" => new NameComparer(true),
- "alphabet-desc" => new NameComparer(false),
- _ => null
- };
-
- return solicitorService.GetSolicitorSummariesAsync(
- new Pagination(pageNumber, pageSize),
- ratingsProvider,
- filter,
- ordering,
- cancellationToken: cancellationToken);
- }
-);
-
-app.MapGet(
- "/cities",
- (
- [FromServices] IReadOnlyCitiesService citiesService,
- CancellationToken cancellationToken) =>
- {
- return citiesService.GetAllCitiesAsync(cancellationToken);
- }
-);
-
-app.MapGet(
- "/conveyancors/{id}",
- (
- [FromServices] IReadOnlySolicitorService solicitorService,
- [FromRoute] Guid id,
- CancellationToken cancellationToken
- ) => solicitorService.GetSolicitorInfoByIdAsync(id, cancellationToken)
-);
-
-app.MapGet(
- "/ratingsProviders",
- (
- [FromServices] IRatingsProviderService service,
- CancellationToken cancellationToken
- ) =>
- {
- return service.GetRatingsProvidersAsync(cancellationToken);
- }
-);
-
-using (var scope = app.Services.CreateScope())
-{
- var solicitors = scope.ServiceProvider.GetRequiredService();
- var first = await solicitors
- .GetSolicitorSummariesAsync(
- new Pagination(1, 1),
- "Solicitors.com",
- cancellationToken: CancellationToken.None);
- if (first.Data.Length == 0)
- await scope.ServiceProvider
- .GetRequiredService()
- .RunFullImport(CancellationToken.None);
-}
-
-app.Run();
-
-public class RatingComparer(string ratingsProvider, bool ascending) : IComparer
-{
- public int Compare(Solicitor? x, Solicitor? y)
- {
- var xRating = x?.Ratings?.FirstOrDefault(r => r.Provider == ratingsProvider);
- var yRating = y?.Ratings?.FirstOrDefault(r => r.Provider == ratingsProvider);
-
- decimal compare;
-
- if (xRating is null)
- compare = yRating is null ? 0 : 1;
- else if (yRating is null)
- compare = -1;
- else
- {
- compare = (xRating.Value / xRating.Maximum) - (yRating.Value / yRating.Maximum);
-
- if (!ascending)
- compare *= -1;
- }
-
- if (compare >= 0)
- return (int)Math.Ceiling(compare);
- else
- return (int)Math.Floor(compare);
-
- }
-}
-
-public class NameComparer(bool ascending) : IComparer
-{
- public int Compare(Solicitor? x, Solicitor? y)
- {
- int compare;
- if (ReferenceEquals(x, y))
- compare = 0;
- else if (y is null)
- compare = 1;
- else if (x is null)
- compare = -1;
- else
- {
- compare = string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
-
- if (!ascending)
- compare *= -1;
- }
-
- return compare;
- }
-}
\ No newline at end of file
diff --git a/backend/SolicitorsApi/SolicitorsApi.http b/backend/SolicitorsApi/SolicitorsApi.http
deleted file mode 100644
index a6c02e2..0000000
--- a/backend/SolicitorsApi/SolicitorsApi.http
+++ /dev/null
@@ -1,6 +0,0 @@
-@SolicitorsApi_HostAddress = http://localhost:5084
-
-GET {{SolicitorsApi_HostAddress}}/weatherforecast/
-Accept: application/json
-
-###
diff --git a/backend/SolicitorsApi/appsettings.Development.json b/backend/SolicitorsApi/appsettings.Development.json
deleted file mode 100644
index ff66ba6..0000000
--- a/backend/SolicitorsApi/appsettings.Development.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- }
-}
diff --git a/backend/Tester/Program.cs b/backend/Tester/Program.cs
deleted file mode 100644
index 1d6f986..0000000
--- a/backend/Tester/Program.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Solicitors.HtmlParsing;
-
-var _html = File.ReadAllText("/var/home/fox/main.html");
-var services = new ServiceCollection();
-services.AddHtmlParsing();
-var parser = services.BuildServiceProvider().GetRequiredService();
-
-var htmlNodes = parser.ParseHtml(_html).ToArray();
-Console.ReadKey();
\ No newline at end of file
diff --git a/backend/Tester/Tester.csproj b/backend/Tester/Tester.csproj
deleted file mode 100644
index dd248af..0000000
--- a/backend/Tester/Tester.csproj
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- Exe
- net10.0
- enable
- enable
-
-
-
-
- ..\..\..\..\..\..\..\..\var\home\fox\.dotnet\shared\Microsoft.AspNetCore.App\10.0.7\Microsoft.Extensions.DependencyInjection.Abstractions.dll
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/backend/app/Solicitors.Api/Endpoints/CitiesEndpoints.cs b/backend/app/Solicitors.Api/Endpoints/CitiesEndpoints.cs
new file mode 100644
index 0000000..76f3cc3
--- /dev/null
+++ b/backend/app/Solicitors.Api/Endpoints/CitiesEndpoints.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Mvc;
+using Solicitors.Core;
+
+namespace Solicitors.Api.Endpoints;
+
+public static class CitiesEndpoints
+{
+ public static void MapCities(this IEndpointRouteBuilder app)
+ {
+ app.MapGet(
+ "/cities",
+ (
+ [FromServices] IReadOnlyCitiesService citiesService,
+ CancellationToken cancellationToken
+ ) => citiesService.GetAllCitiesAsync(cancellationToken));
+ }
+}
\ No newline at end of file
diff --git a/backend/app/Solicitors.Api/Endpoints/RatingsProvidersEndpoints.cs b/backend/app/Solicitors.Api/Endpoints/RatingsProvidersEndpoints.cs
new file mode 100644
index 0000000..2575281
--- /dev/null
+++ b/backend/app/Solicitors.Api/Endpoints/RatingsProvidersEndpoints.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Mvc;
+using Solicitors.Core;
+
+namespace Solicitors.Api.Endpoints;
+
+public static class RatingsProvidersEndpoints
+{
+ public static void MapRatingsProviders(this IEndpointRouteBuilder app)
+ {
+ app.MapGet(
+ "/ratingsProviders",
+ (
+ [FromServices] IRatingsProviderService service,
+ CancellationToken cancellationToken
+ ) => service.GetRatingsProvidersAsync(cancellationToken));
+ }
+}
\ No newline at end of file
diff --git a/backend/app/Solicitors.Api/Endpoints/SolicitorsEndpoints.cs b/backend/app/Solicitors.Api/Endpoints/SolicitorsEndpoints.cs
new file mode 100644
index 0000000..df673da
--- /dev/null
+++ b/backend/app/Solicitors.Api/Endpoints/SolicitorsEndpoints.cs
@@ -0,0 +1,60 @@
+using Microsoft.AspNetCore.Mvc;
+using Solicitors.Core;
+using Solicitors.Core.Misc;
+using Solicitors.Core.Models;
+using Solicitors.Core.Ordering;
+
+namespace Solicitors.Api.Endpoints;
+
+public static class SolicitorsEndpoints
+{
+ public static void MapSolicitors(this IEndpointRouteBuilder app)
+ {
+ app.MapGet(
+ "/solicitors",
+ (
+ [FromServices] IReadOnlySolicitorService solicitorService,
+ [FromServices] ISolicitorComparerFactory solicitorComparerFactory,
+ CancellationToken cancellationToken,
+ [FromQuery] uint pageNumber = 1,
+ [FromQuery] uint pageSize = 20,
+ [FromQuery] string? nameFilter = null,
+ [FromQuery] decimal minRating = 0,
+ [FromQuery] string[]? cities = null,
+ [FromQuery] string ratingsProvider = "Solicitors.com",
+ [FromQuery] OrderingType? orderingType = null) =>
+ {
+ IFilter? filter = null;
+
+ if (!string.IsNullOrEmpty(nameFilter))
+ filter = new WrapperFilter(filter, solicitor => solicitor.Name.Contains(nameFilter));
+
+ if (cities is not null && cities.Length > 0)
+ filter = new WrapperFilter(filter, solicitor => cities.Any(city => solicitor.Cities.Any(solCity => solCity.Name == city)));
+
+ if (minRating > 0)
+ filter = new WrapperFilter(
+ filter,
+ solicitor => solicitor.Ratings.Any(rating => rating.Provider == ratingsProvider && rating.Value / rating.Maximum >= minRating / 5.0m));
+
+ var ordering = solicitorComparerFactory.GetComparer(orderingType, ratingsProvider);
+
+ return solicitorService.GetSolicitorSummariesAsync(
+ new Pagination(pageNumber, pageSize),
+ ratingsProvider,
+ filter,
+ ordering,
+ cancellationToken: cancellationToken);
+ }
+ );
+
+ app.MapGet(
+ "/solicitors/{id:guid}",
+ (
+ [FromServices] IReadOnlySolicitorService solicitorService,
+ [FromRoute] Guid id,
+ CancellationToken cancellationToken
+ ) => solicitorService.GetSolicitorInfoByIdAsync(id, cancellationToken)
+ );
+ }
+}
\ No newline at end of file
diff --git a/backend/app/Solicitors.Api/Program.cs b/backend/app/Solicitors.Api/Program.cs
new file mode 100644
index 0000000..7b19b30
--- /dev/null
+++ b/backend/app/Solicitors.Api/Program.cs
@@ -0,0 +1,41 @@
+using Solicitors.Api.Endpoints;
+using Solicitors.CacheBuild;
+using Solicitors.Core;
+using Solicitors.Data;
+using Solicitors.HtmlParsing;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Configuration.AddJsonFile("/data/apiSettings.json", optional: true);
+
+builder.Services.AddOpenApi();
+builder.Services.AddCore();
+builder.Services.AddData(builder.Configuration);
+builder.Services.AddCacheBuild();
+builder.Services.Configure(builder.Configuration.GetSection("Imports"));
+builder.Services.AddHtmlParsing();
+builder.Services.AddHttpClient();
+builder.Services.AddCors(options =>
+{
+ options.AddDefaultPolicy(policy => policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
+});
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.MapOpenApi();
+}
+
+app.UseHttpsRedirection();
+app.MapSolicitors();
+app.MapCities();
+app.MapRatingsProviders();
+
+using (var scope = app.Services.CreateScope())
+{
+ await scope.UseCacheBuildAsync(CancellationToken.None);
+}
+
+app.Run();
\ No newline at end of file
diff --git a/backend/SolicitorsApi/Properties/launchSettings.json b/backend/app/Solicitors.Api/Properties/launchSettings.json
similarity index 77%
rename from backend/SolicitorsApi/Properties/launchSettings.json
rename to backend/app/Solicitors.Api/Properties/launchSettings.json
index 3abbdbf..9ac83b5 100644
--- a/backend/SolicitorsApi/Properties/launchSettings.json
+++ b/backend/app/Solicitors.Api/Properties/launchSettings.json
@@ -5,7 +5,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
- "applicationUrl": "http://localhost:5084",
+ "applicationUrl": "http://localhost:5254",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -14,7 +14,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
- "applicationUrl": "https://localhost:7128;http://localhost:5084",
+ "applicationUrl": "https://localhost:7226;http://localhost:5254",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
diff --git a/backend/SolicitorsApi/SolicitorsApi.csproj b/backend/app/Solicitors.Api/Solicitors.Api.csproj
similarity index 61%
rename from backend/SolicitorsApi/SolicitorsApi.csproj
rename to backend/app/Solicitors.Api/Solicitors.Api.csproj
index 28d473a..4765b66 100644
--- a/backend/SolicitorsApi/SolicitorsApi.csproj
+++ b/backend/app/Solicitors.Api/Solicitors.Api.csproj
@@ -15,10 +15,8 @@
-
-
-
-
+
+
diff --git a/backend/app/Solicitors.Api/Solicitors.Api.http b/backend/app/Solicitors.Api/Solicitors.Api.http
new file mode 100644
index 0000000..a8e024f
--- /dev/null
+++ b/backend/app/Solicitors.Api/Solicitors.Api.http
@@ -0,0 +1,6 @@
+@Solicitors.Api_HostAddress = http://localhost:5254
+
+GET {{Solicitors.Api_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/backend/SolicitorsApi/appsettings.json b/backend/app/Solicitors.Api/appsettings.json
similarity index 92%
rename from backend/SolicitorsApi/appsettings.json
rename to backend/app/Solicitors.Api/appsettings.json
index 4d56694..149cb09 100644
--- a/backend/SolicitorsApi/appsettings.json
+++ b/backend/app/Solicitors.Api/appsettings.json
@@ -6,4 +6,4 @@
}
},
"AllowedHosts": "*"
-}
+}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.CacheBuild/DIExtensions.cs b/backend/libs/Solicitors.CacheBuild/DIExtensions.cs
index 4b8ab16..37f832c 100644
--- a/backend/libs/Solicitors.CacheBuild/DIExtensions.cs
+++ b/backend/libs/Solicitors.CacheBuild/DIExtensions.cs
@@ -1,5 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Quartz;
namespace Solicitors.CacheBuild;
@@ -10,6 +12,43 @@ public static class DIExtensions
{
return services
.AddScoped()
- .AddScoped();
+ .AddScoped()
+ .AddQuartz()
+ .AddQuartzHostedService(opt => opt.WaitForJobsToComplete = true);
+ }
+
+ public static async Task UseCacheBuildAsync(this IServiceScope scope, CancellationToken cancellationToken)
+ {
+ var factory = scope.ServiceProvider.GetRequiredService();
+ var options = scope.ServiceProvider.GetRequiredService>().Value;
+ var scheduler = await factory.GetScheduler(cancellationToken);
+
+ var importJob = JobBuilder.Create()
+ .WithIdentity("ImportJob", "ImportGroup")
+ .Build();
+
+ var importTrigger = TriggerBuilder.Create()
+ .WithIdentity("ImportRunner", "ImportGroup")
+ .StartNow()
+ .WithSimpleSchedule(builder => builder
+ .WithIntervalInMinutes(options.ImportMinutes)
+ .RepeatForever())
+ .Build();
+
+ await scheduler.ScheduleJob(importJob, importTrigger, cancellationToken);
+
+ var staleJob = JobBuilder.Create()
+ .WithIdentity("StaleDataRemover", "RemoveGroup")
+ .Build();
+
+ var staleTrigger = TriggerBuilder.Create()
+ .WithIdentity("StaleDataTrigger", "RemoveGroup")
+ .StartNow()
+ .WithSimpleSchedule(builder => builder
+ .WithIntervalInMinutes(options.RemoveMinutes)
+ .RepeatForever())
+ .Build();
+
+ await scheduler.ScheduleJob(staleJob, staleTrigger, cancellationToken);
}
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.CacheBuild/ImportConfiguration.cs b/backend/libs/Solicitors.CacheBuild/ImportConfiguration.cs
new file mode 100644
index 0000000..70a8ef6
--- /dev/null
+++ b/backend/libs/Solicitors.CacheBuild/ImportConfiguration.cs
@@ -0,0 +1,8 @@
+namespace Solicitors.CacheBuild;
+
+public class ImportConfiguration
+{
+ public int StaleMinutes { get; set; }
+ public int RemoveMinutes { get; set; }
+ public int ImportMinutes { get; set; }
+}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.CacheBuild/ImportRunner.cs b/backend/libs/Solicitors.CacheBuild/ImportRunner.cs
new file mode 100644
index 0000000..72cefb7
--- /dev/null
+++ b/backend/libs/Solicitors.CacheBuild/ImportRunner.cs
@@ -0,0 +1,15 @@
+using Quartz;
+using Solicitors.Core.Data;
+
+namespace Solicitors.CacheBuild;
+
+internal class ImportRunner(
+ ISolicitorImporter importer,
+ ISolicitorRepository repository) : IJob
+{
+ public async Task Execute(IJobExecutionContext context)
+ {
+ await repository.EnsureCreatedAsync(context.CancellationToken);
+ await importer.RunFullImport(context.CancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.CacheBuild/SolicitorImporter.cs b/backend/libs/Solicitors.CacheBuild/SolicitorImporter.cs
index 348be45..220c074 100644
--- a/backend/libs/Solicitors.CacheBuild/SolicitorImporter.cs
+++ b/backend/libs/Solicitors.CacheBuild/SolicitorImporter.cs
@@ -3,27 +3,18 @@ using Solicitors.Core.Models.Imports;
namespace Solicitors.CacheBuild;
-internal class SolicitorImporter : ISolicitorImporter
+internal class SolicitorImporter(
+ ISolicitorParser parser,
+ ISolicitorRepository repository) : ISolicitorImporter
{
- private readonly ISolicitorParser _parser;
- private readonly ISolicitorRepository _repository;
-
- public SolicitorImporter(
- ISolicitorParser parser,
- ISolicitorRepository repository)
- {
- _parser = parser;
- _repository = repository;
- }
-
public async Task RunFullImport(CancellationToken cancellationToken)
{
- foreach (var solicitor in await _parser.GetSolicitorsAsync(cancellationToken))
+ foreach (var solicitor in await parser.GetSolicitorsAsync(cancellationToken))
{
await RunImport(solicitor, cancellationToken);
}
}
private Task RunImport(SolicitorData solicitor, CancellationToken cancellationToken)
- => _repository.AddOrUpdateSolicitorAsync(solicitor, cancellationToken);
+ => repository.AddOrUpdateSolicitorAsync(solicitor, cancellationToken);
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.CacheBuild/Solicitors.CacheBuild.csproj b/backend/libs/Solicitors.CacheBuild/Solicitors.CacheBuild.csproj
index 66de5d2..7e73086 100644
--- a/backend/libs/Solicitors.CacheBuild/Solicitors.CacheBuild.csproj
+++ b/backend/libs/Solicitors.CacheBuild/Solicitors.CacheBuild.csproj
@@ -13,6 +13,15 @@
+
+
+
+
+
+
+
+ ..\..\..\..\..\..\..\.dotnet\shared\Microsoft.AspNetCore.App\10.0.7\Microsoft.Extensions.Hosting.Abstractions.dll
+
diff --git a/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/SolicitorParser.cs b/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/SolicitorParser.cs
index 76b56b1..b2a6460 100644
--- a/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/SolicitorParser.cs
+++ b/backend/libs/Solicitors.CacheBuild/SolicitorsDotCom/SolicitorParser.cs
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
-using System.Net.Http.Headers;
using Solicitors.Core.Misc;
using Solicitors.Core.Models.Imports;
using Solicitors.HtmlParsing;
@@ -154,7 +153,7 @@ internal class SolicitorParser : ISolicitorParser
ParseSidebar(sidebarChildren, out phone, out email, out website, out ratings);
}
- return new SolicitorData()
+ return new SolicitorData
{
Name = builder.Name,
UrlPath = builder.Path,
@@ -168,14 +167,14 @@ internal class SolicitorParser : ISolicitorParser
};
}
- Location[] ParseOffices(IEnumerable officeNodes)
+ private Location[] ParseOffices(IEnumerable officeNodes)
=> officeNodes
.Select(ParseOffice)
.Where(x => x is not null)
.Cast()
.ToArray();
- Location? ParseOffice(IHtmlNode officeNode)
+ private Location? ParseOffice(IHtmlNode officeNode)
{
if (!officeNode.TryGetChildren(out var officeChildren)
|| !officeNode.TryGetByTagName("address", out var addressNode)
@@ -183,7 +182,7 @@ internal class SolicitorParser : ISolicitorParser
return null;
Location? location = null;
- string? address = "";
+ var address = "";
foreach (var line in addressLines)
{
if (line.TryGetText(out var lineText))
@@ -214,7 +213,7 @@ internal class SolicitorParser : ISolicitorParser
return location;
}
- void ParseSidebar(
+ private void ParseSidebar(
IEnumerable sidebarChildren,
out string? phone,
out string? email,
@@ -253,7 +252,7 @@ internal class SolicitorParser : ISolicitorParser
foreach (var child in childrenArray.Where(x => x.TryGetByClass("rev-box", out _, true)))
{
if (child.TryGetAttributeValue("title", out var ratingString)
- && TryParseRatingString(ratingString, out decimal rating, out decimal maxRating)
+ && TryParseRatingString(ratingString, out var rating, out var maxRating)
&& child.TryGetByTagNameAndClass("img", "rev-logo", out var imgNode)
&& imgNode.TryGetAttributeValue("src", out var imgSrc)
&& TryParseRatingSrc(imgSrc, out var ratingProvider))
@@ -290,15 +289,15 @@ internal class SolicitorParser : ISolicitorParser
return decimal.TryParse(parts[0], out rating) && decimal.TryParse(parts[1], out maxRating);
}
- bool TryParseRatingSrc(string imgSrc, out string provider)
+ private bool TryParseRatingSrc(string imgSrc, out string provider)
{
provider = "";
imgSrc = imgSrc.Trim('"');
- var prefix = "/images/logo-";
+ const string prefix = "/images/logo-";
if (!imgSrc.StartsWith(prefix))
return false;
- var providerAndExtension = imgSrc.Substring(prefix.Length);
+ var providerAndExtension = imgSrc[prefix.Length..];
provider = providerAndExtension.Split('.').First();
return true;
}
@@ -306,11 +305,11 @@ internal class SolicitorParser : ISolicitorParser
bool TryParseOwnRatingString(string ratingString, out decimal rating)
{
rating = 0;
- var prefix = "Average review score : ";
+ const string prefix = "Average review score : ";
if (!ratingString.StartsWith(prefix))
return false;
- var ratingPart = ratingString.Substring(prefix.Length);
+ var ratingPart = ratingString[prefix.Length..];
return decimal.TryParse(ratingPart, out rating);
}
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.CacheBuild/StaleDataRemover.cs b/backend/libs/Solicitors.CacheBuild/StaleDataRemover.cs
new file mode 100644
index 0000000..66684af
--- /dev/null
+++ b/backend/libs/Solicitors.CacheBuild/StaleDataRemover.cs
@@ -0,0 +1,19 @@
+using Microsoft.Extensions.Options;
+using Quartz;
+using Solicitors.Core.Data;
+
+namespace Solicitors.CacheBuild;
+
+internal class StaleDataRemover(
+ IOptions config,
+ ISolicitorRepository repository) : IJob
+{
+ private readonly ImportConfiguration _config = config.Value;
+
+ public Task Execute(IJobExecutionContext context)
+ {
+ return repository.RemoveStaleEntriesAsync(
+ TimeSpan.FromMinutes(_config.StaleMinutes),
+ context.CancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/DIExtensions.cs b/backend/libs/Solicitors.Core/DIExtensions.cs
index b7feb4f..8111f79 100644
--- a/backend/libs/Solicitors.Core/DIExtensions.cs
+++ b/backend/libs/Solicitors.Core/DIExtensions.cs
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
+using Solicitors.Core.Ordering;
namespace Solicitors.Core;
@@ -12,6 +13,7 @@ public static class DIExtensions
.AddScoped()
.AddScoped(sp => sp.GetRequiredService())
.AddScoped()
- .AddScoped();
+ .AddScoped()
+ .AddScoped();
}
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Data/IReadOnlySolicitorRepository.cs b/backend/libs/Solicitors.Core/Data/IReadOnlySolicitorRepository.cs
index 7314b88..d0d2293 100644
--- a/backend/libs/Solicitors.Core/Data/IReadOnlySolicitorRepository.cs
+++ b/backend/libs/Solicitors.Core/Data/IReadOnlySolicitorRepository.cs
@@ -10,5 +10,5 @@ public interface IReadOnlySolicitorRepository
public interface IReadOnlyCitiesRepository
{
- IAsyncEnumerable GetCitiesAsync(CancellationToken cancellationToken);
+ IAsyncEnumerable GetCitiesAsync();
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Data/ISolicitorRepository.cs b/backend/libs/Solicitors.Core/Data/ISolicitorRepository.cs
index 72dd972..a2227e2 100644
--- a/backend/libs/Solicitors.Core/Data/ISolicitorRepository.cs
+++ b/backend/libs/Solicitors.Core/Data/ISolicitorRepository.cs
@@ -4,5 +4,7 @@ namespace Solicitors.Core.Data;
public interface ISolicitorRepository : IReadOnlySolicitorRepository
{
+ Task EnsureCreatedAsync(CancellationToken cancellationToken);
Task AddOrUpdateSolicitorAsync(SolicitorData solicitor, CancellationToken cancellationToken);
+ Task RemoveStaleEntriesAsync(TimeSpan staleAge, CancellationToken cancellationToken);
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/IReadOnlySolicitorService.cs b/backend/libs/Solicitors.Core/IReadOnlySolicitorService.cs
index 030270a..e2db2fd 100644
--- a/backend/libs/Solicitors.Core/IReadOnlySolicitorService.cs
+++ b/backend/libs/Solicitors.Core/IReadOnlySolicitorService.cs
@@ -1,5 +1,6 @@
using Solicitors.Core.Misc;
using Solicitors.Core.Models;
+using Solicitors.Core.Models.View;
namespace Solicitors.Core;
diff --git a/backend/libs/Solicitors.Core/ISolicitorService.cs b/backend/libs/Solicitors.Core/ISolicitorService.cs
index 1bc9d56..78aa8bb 100644
--- a/backend/libs/Solicitors.Core/ISolicitorService.cs
+++ b/backend/libs/Solicitors.Core/ISolicitorService.cs
@@ -1,6 +1,3 @@
namespace Solicitors.Core;
-public interface ISolicitorService : IReadOnlySolicitorService
-{
-
-}
\ No newline at end of file
+public interface ISolicitorService : IReadOnlySolicitorService;
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Misc/IFilter.cs b/backend/libs/Solicitors.Core/Misc/IFilter.cs
index 8e82d43..c12c07b 100644
--- a/backend/libs/Solicitors.Core/Misc/IFilter.cs
+++ b/backend/libs/Solicitors.Core/Misc/IFilter.cs
@@ -1,29 +1,19 @@
namespace Solicitors.Core.Misc;
-public interface IFilter
+public interface IFilter
{
bool Filter(T item);
}
-public class WrapperFilter : IFilter
+public class WrapperFilter(
+ IFilter? inner,
+ Func filter) : IFilter
{
- private readonly Func _filter;
- private readonly IFilter? _inner;
-
- public WrapperFilter(
- IFilter? inner,
- Func filter)
- {
- _inner = inner;
- _filter = filter;
- }
-
-
public bool Filter(T item)
{
- if (_inner is not null)
- return _inner.Filter(item) && _filter(item);
+ if (inner is not null)
+ return inner.Filter(item) && filter(item);
- return _filter(item);
+ return filter(item);
}
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Models/City.cs b/backend/libs/Solicitors.Core/Models/City.cs
index f183649..5ba38e1 100644
--- a/backend/libs/Solicitors.Core/Models/City.cs
+++ b/backend/libs/Solicitors.Core/Models/City.cs
@@ -4,6 +4,7 @@ public record City
{
public Guid CityId { get; set; }
public required string Name { get; set; }
+ public DateTime LastModified { get; set; }
- public List Solicitors { get; } = new();
+ public List Solicitors { get; } = [];
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Models/Imports/Location.cs b/backend/libs/Solicitors.Core/Models/Imports/Location.cs
index 14c6fb6..e70a309 100644
--- a/backend/libs/Solicitors.Core/Models/Imports/Location.cs
+++ b/backend/libs/Solicitors.Core/Models/Imports/Location.cs
@@ -1,8 +1,8 @@
namespace Solicitors.Core.Models.Imports;
-public record Location(string address, string phone, Rating[] locationRatings)
+public record Location(string Address, string Phone, Rating[] LocationRatings)
{
- public string Address { get; } = address;
- public string Phone { get; } = phone;
- public Rating[] LocationRatings { get; } = locationRatings;
+ public string Address { get; } = Address;
+ public string Phone { get; } = Phone;
+ public Rating[] LocationRatings { get; } = LocationRatings;
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Models/Imports/Rating.cs b/backend/libs/Solicitors.Core/Models/Imports/Rating.cs
index 7893907..da987e4 100644
--- a/backend/libs/Solicitors.Core/Models/Imports/Rating.cs
+++ b/backend/libs/Solicitors.Core/Models/Imports/Rating.cs
@@ -1,9 +1,9 @@
namespace Solicitors.Core.Models.Imports;
-public record Rating(decimal value, decimal maxValue, string provider, string imgSrc)
+public record Rating(decimal Value, decimal MaxValue, string RatingProvider, string RatingProviderImgSrc)
{
- public decimal Value { get; } = value;
- public decimal MaxValue { get; } = maxValue;
- public string RatingProvider { get; } = provider;
- public string RatingProviderImgSrc { get; } = imgSrc;
+ public decimal Value { get; } = Value;
+ public decimal MaxValue { get; } = MaxValue;
+ public string RatingProvider { get; } = RatingProvider;
+ public string RatingProviderImgSrc { get; } = RatingProviderImgSrc;
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Models/Solicitor.cs b/backend/libs/Solicitors.Core/Models/Solicitor.cs
index 0172467..01d932a 100644
--- a/backend/libs/Solicitors.Core/Models/Solicitor.cs
+++ b/backend/libs/Solicitors.Core/Models/Solicitor.cs
@@ -11,8 +11,9 @@ public record Solicitor
public string? Email { get; set; }
public string? Website { get; set; }
public string? ShortDescription { get; set; }
+ public DateTime LastModified { get; set; }
- public List Cities { get; } = new();
- public List Ratings { get; } = new();
- public List Locations { get; } = new();
+ public List Cities { get; } = [];
+ public List Ratings { get; } = [];
+ public List Locations { get; } = [];
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Models/SolicitorInfo.cs b/backend/libs/Solicitors.Core/Models/SolicitorInfo.cs
index ac7c5a2..3691859 100644
--- a/backend/libs/Solicitors.Core/Models/SolicitorInfo.cs
+++ b/backend/libs/Solicitors.Core/Models/SolicitorInfo.cs
@@ -1,3 +1,5 @@
+using Solicitors.Core.Models.View;
+
namespace Solicitors.Core.Models;
public record SolicitorInfo
@@ -25,7 +27,7 @@ public record SolicitorInfo
}
public string Name { get; set; }
- public string ShortDescription { get; set; }
+ public string? ShortDescription { get; set; }
public Guid Id { get; set; }
public string? Phone { get; set; }
diff --git a/backend/libs/Solicitors.Core/Models/SolicitorLocation.cs b/backend/libs/Solicitors.Core/Models/SolicitorLocation.cs
index 84111b3..a9e787e 100644
--- a/backend/libs/Solicitors.Core/Models/SolicitorLocation.cs
+++ b/backend/libs/Solicitors.Core/Models/SolicitorLocation.cs
@@ -6,8 +6,9 @@ public record SolicitorLocation
public required string Address { get; set; }
public required string Phone { get; set; }
+ public DateTime LastModified { get; set; }
- public List LocationRatings { get; } = new();
+ public List LocationRatings { get; } = [];
public Guid SolicitorId { get; set; }
public required Solicitor Solicitor { get; set; }
diff --git a/backend/libs/Solicitors.Core/Models/SolicitorLocationRating.cs b/backend/libs/Solicitors.Core/Models/SolicitorLocationRating.cs
index f578ac8..671035a 100644
--- a/backend/libs/Solicitors.Core/Models/SolicitorLocationRating.cs
+++ b/backend/libs/Solicitors.Core/Models/SolicitorLocationRating.cs
@@ -7,6 +7,7 @@ public record SolicitorLocationRating : IRating
public required decimal Value { get; set; }
public required decimal Maximum { get; set; }
public required string Provider { get; set; }
+ public DateTime LastModified { get; set; }
public Guid SolicitorLocationId { get; set; }
public required SolicitorLocation SolicitorLocation { get; set; }
diff --git a/backend/libs/Solicitors.Core/Models/SolicitorRating.cs b/backend/libs/Solicitors.Core/Models/SolicitorRating.cs
index e8edae2..5c9a702 100644
--- a/backend/libs/Solicitors.Core/Models/SolicitorRating.cs
+++ b/backend/libs/Solicitors.Core/Models/SolicitorRating.cs
@@ -7,6 +7,7 @@ public record SolicitorRating : IRating
public required decimal Value { get; set; }
public required decimal Maximum { get; set; }
public required string Provider { get; set; }
+ public DateTime LastModified { get; set; }
public Guid SolicitorId { get; set; }
public required Solicitor Solicitor { get; set; }
diff --git a/backend/libs/Solicitors.Core/Models/LocationInfo.cs b/backend/libs/Solicitors.Core/Models/View/LocationInfo.cs
similarity index 91%
rename from backend/libs/Solicitors.Core/Models/LocationInfo.cs
rename to backend/libs/Solicitors.Core/Models/View/LocationInfo.cs
index 63ef4d6..41e3d2a 100644
--- a/backend/libs/Solicitors.Core/Models/LocationInfo.cs
+++ b/backend/libs/Solicitors.Core/Models/View/LocationInfo.cs
@@ -1,4 +1,4 @@
-namespace Solicitors.Core.Models;
+namespace Solicitors.Core.Models.View;
public record LocationInfo
{
diff --git a/backend/libs/Solicitors.Core/Models/RatingInfo.cs b/backend/libs/Solicitors.Core/Models/View/RatingInfo.cs
similarity index 88%
rename from backend/libs/Solicitors.Core/Models/RatingInfo.cs
rename to backend/libs/Solicitors.Core/Models/View/RatingInfo.cs
index 9564194..aed300f 100644
--- a/backend/libs/Solicitors.Core/Models/RatingInfo.cs
+++ b/backend/libs/Solicitors.Core/Models/View/RatingInfo.cs
@@ -1,4 +1,4 @@
-namespace Solicitors.Core.Models;
+namespace Solicitors.Core.Models.View;
public record RatingInfo
{
diff --git a/backend/libs/Solicitors.Core/Models/SolicitorSummary.cs b/backend/libs/Solicitors.Core/Models/View/SolicitorSummary.cs
similarity index 93%
rename from backend/libs/Solicitors.Core/Models/SolicitorSummary.cs
rename to backend/libs/Solicitors.Core/Models/View/SolicitorSummary.cs
index da5f195..d2ce52d 100644
--- a/backend/libs/Solicitors.Core/Models/SolicitorSummary.cs
+++ b/backend/libs/Solicitors.Core/Models/View/SolicitorSummary.cs
@@ -1,4 +1,4 @@
-namespace Solicitors.Core.Models;
+namespace Solicitors.Core.Models.View;
public record SolicitorSummary
{
diff --git a/backend/libs/Solicitors.Core/Misc/DefaultSolicitorDataOrdering.cs b/backend/libs/Solicitors.Core/Ordering/DefaultSolicitorDataOrdering.cs
similarity index 93%
rename from backend/libs/Solicitors.Core/Misc/DefaultSolicitorDataOrdering.cs
rename to backend/libs/Solicitors.Core/Ordering/DefaultSolicitorDataOrdering.cs
index 7621939..59f4b28 100644
--- a/backend/libs/Solicitors.Core/Misc/DefaultSolicitorDataOrdering.cs
+++ b/backend/libs/Solicitors.Core/Ordering/DefaultSolicitorDataOrdering.cs
@@ -1,6 +1,6 @@
using Solicitors.Core.Models;
-namespace Solicitors.Core.Misc;
+namespace Solicitors.Core.Ordering;
internal class DefaultSolicitorDataOrdering : IComparer
{
diff --git a/backend/libs/Solicitors.Core/Ordering/ISolicitorComparerFactory.cs b/backend/libs/Solicitors.Core/Ordering/ISolicitorComparerFactory.cs
new file mode 100644
index 0000000..69507b4
--- /dev/null
+++ b/backend/libs/Solicitors.Core/Ordering/ISolicitorComparerFactory.cs
@@ -0,0 +1,10 @@
+using Solicitors.Core.Models;
+
+namespace Solicitors.Core.Ordering;
+
+public interface ISolicitorComparerFactory
+{
+ IComparer GetComparer(
+ OrderingType? orderingType,
+ string ratingsProvider);
+}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Ordering/NameComparer.cs b/backend/libs/Solicitors.Core/Ordering/NameComparer.cs
new file mode 100644
index 0000000..ddd9487
--- /dev/null
+++ b/backend/libs/Solicitors.Core/Ordering/NameComparer.cs
@@ -0,0 +1,26 @@
+using Solicitors.Core.Models;
+
+namespace Solicitors.Core.Ordering;
+
+internal class NameComparer(bool ascending) : IComparer
+{
+ public int Compare(Solicitor? x, Solicitor? y)
+ {
+ int compare;
+ if (ReferenceEquals(x, y))
+ compare = 0;
+ else if (y is null)
+ compare = 1;
+ else if (x is null)
+ compare = -1;
+ else
+ {
+ compare = string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
+
+ if (!ascending)
+ compare *= -1;
+ }
+
+ return compare;
+ }
+}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Ordering/OrderingType.cs b/backend/libs/Solicitors.Core/Ordering/OrderingType.cs
new file mode 100644
index 0000000..1a3d38f
--- /dev/null
+++ b/backend/libs/Solicitors.Core/Ordering/OrderingType.cs
@@ -0,0 +1,9 @@
+namespace Solicitors.Core.Ordering;
+
+public enum OrderingType
+{
+ RatingDescending,
+ RatingAscending,
+ AlphabetAscending,
+ AlphabetDescending
+}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Ordering/RatingComparer.cs b/backend/libs/Solicitors.Core/Ordering/RatingComparer.cs
new file mode 100644
index 0000000..7714bd7
--- /dev/null
+++ b/backend/libs/Solicitors.Core/Ordering/RatingComparer.cs
@@ -0,0 +1,32 @@
+using Solicitors.Core.Models;
+
+namespace Solicitors.Core.Ordering;
+
+internal class RatingComparer(string ratingsProvider, bool ascending) : IComparer
+{
+ public int Compare(Solicitor? x, Solicitor? y)
+ {
+ var xRating = x?.Ratings.FirstOrDefault(r => r.Provider == ratingsProvider);
+ var yRating = y?.Ratings.FirstOrDefault(r => r.Provider == ratingsProvider);
+
+ decimal compare;
+
+ if (xRating is null)
+ compare = yRating is null ? 0 : 1;
+ else if (yRating is null)
+ compare = -1;
+ else
+ {
+ compare = (xRating.Value / xRating.Maximum) - (yRating.Value / yRating.Maximum);
+
+ if (!ascending)
+ compare *= -1;
+ }
+
+ if (compare >= 0)
+ return (int)Math.Ceiling(compare);
+ else
+ return (int)Math.Floor(compare);
+
+ }
+}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/Ordering/SolicitorComparerFactory.cs b/backend/libs/Solicitors.Core/Ordering/SolicitorComparerFactory.cs
new file mode 100644
index 0000000..784ec2f
--- /dev/null
+++ b/backend/libs/Solicitors.Core/Ordering/SolicitorComparerFactory.cs
@@ -0,0 +1,20 @@
+using Solicitors.Core.Models;
+
+namespace Solicitors.Core.Ordering;
+
+internal class SolicitorComparerFactory : ISolicitorComparerFactory
+{
+ public IComparer GetComparer(
+ OrderingType? orderingType,
+ string ratingsProvider)
+ {
+ return orderingType switch
+ {
+ OrderingType.RatingAscending => new RatingComparer(ratingsProvider, true),
+ OrderingType.RatingDescending => new RatingComparer(ratingsProvider, false),
+ OrderingType.AlphabetAscending => new NameComparer(true),
+ OrderingType.AlphabetDescending => new NameComparer(false),
+ _ => DefaultSolicitorDataOrdering.Instance
+ };
+ }
+}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/RatingsProviderService.cs b/backend/libs/Solicitors.Core/RatingsProviderService.cs
index e4f2ed8..7fe71a5 100644
--- a/backend/libs/Solicitors.Core/RatingsProviderService.cs
+++ b/backend/libs/Solicitors.Core/RatingsProviderService.cs
@@ -2,17 +2,10 @@ using Solicitors.Core.Data;
namespace Solicitors.Core;
-internal class RatingsProviderService : IRatingsProviderService
+internal class RatingsProviderService(IRatingsProviderRepository repo) : IRatingsProviderService
{
- private readonly IRatingsProviderRepository _repo;
-
- public RatingsProviderService(IRatingsProviderRepository repo)
- {
- _repo = repo;
- }
-
public async Task GetRatingsProvidersAsync(CancellationToken cancellationToken)
{
- return await _repo.GetAllRatingsProvidersAsync().ToArrayAsync(cancellationToken);
+ return await repo.GetAllRatingsProvidersAsync().ToArrayAsync(cancellationToken);
}
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/ReadOnlyCitiesService.cs b/backend/libs/Solicitors.Core/ReadOnlyCitiesService.cs
index 28b8371..19f7e61 100644
--- a/backend/libs/Solicitors.Core/ReadOnlyCitiesService.cs
+++ b/backend/libs/Solicitors.Core/ReadOnlyCitiesService.cs
@@ -2,18 +2,11 @@ using Solicitors.Core.Data;
namespace Solicitors.Core;
-internal class ReadOnlyCitiesService : IReadOnlyCitiesService
+internal class ReadOnlyCitiesService(IReadOnlyCitiesRepository repo) : IReadOnlyCitiesService
{
- private readonly IReadOnlyCitiesRepository _repo;
-
- public ReadOnlyCitiesService(IReadOnlyCitiesRepository repo)
- {
- _repo = repo;
- }
-
public async Task GetAllCitiesAsync(CancellationToken cancellationToken)
{
- var cities = _repo.GetCitiesAsync(cancellationToken);
+ var cities = repo.GetCitiesAsync();
return await cities.ToArrayAsync(cancellationToken);
}
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Core/SolicitorService.cs b/backend/libs/Solicitors.Core/SolicitorService.cs
index ad329a0..114ea43 100644
--- a/backend/libs/Solicitors.Core/SolicitorService.cs
+++ b/backend/libs/Solicitors.Core/SolicitorService.cs
@@ -1,18 +1,13 @@
using Solicitors.Core.Data;
using Solicitors.Core.Misc;
using Solicitors.Core.Models;
+using Solicitors.Core.Models.View;
+using Solicitors.Core.Ordering;
namespace Solicitors.Core;
-internal class SolicitorService : ISolicitorService
+internal class SolicitorService(IReadOnlySolicitorRepository repo) : ISolicitorService
{
- private readonly IReadOnlySolicitorRepository _repo;
-
- public SolicitorService(IReadOnlySolicitorRepository repo)
- {
- _repo = repo;
- }
-
public async Task> GetSolicitorSummariesAsync(
Pagination pagination,
string ratingsProvider,
@@ -20,14 +15,13 @@ internal class SolicitorService : ISolicitorService
IComparer? ordering = null,
CancellationToken cancellationToken = default)
{
- var allSolicitors = _repo.GetAllSolicitorsAsync();
+ var allSolicitors = repo.GetAllSolicitorsAsync();
if (filter is not null)
allSolicitors = allSolicitors.Where(filter.Filter);
var matchingSolicitors = await allSolicitors.ToArrayAsync(cancellationToken);
- if (ordering is null)
- ordering = DefaultSolicitorDataOrdering.Instance;
+ ordering ??= DefaultSolicitorDataOrdering.Instance;
var results = matchingSolicitors
.OrderBy(x => x, ordering)
@@ -41,7 +35,7 @@ internal class SolicitorService : ISolicitorService
public async Task GetSolicitorInfoByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
- var data = await _repo.GetSolicitorByIdAsync(id, cancellationToken);
+ var data = await repo.GetSolicitorByIdAsync(id, cancellationToken);
if (data is null)
return null;
diff --git a/backend/libs/Solicitors.Data/DIExtensions.cs b/backend/libs/Solicitors.Data/DIExtensions.cs
index a8cda75..0c0087a 100644
--- a/backend/libs/Solicitors.Data/DIExtensions.cs
+++ b/backend/libs/Solicitors.Data/DIExtensions.cs
@@ -1,7 +1,6 @@
using System.Diagnostics.CodeAnalysis;
-using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using Solicitors.Core;
using Solicitors.Core.Data;
using Solicitors.Data.RepositorySetup;
using Solicitors.Data.RepositorySetup.InMemory;
@@ -12,7 +11,7 @@ namespace Solicitors.Data;
[SuppressMessage("ReSharper", "InconsistentNaming")]
public static class DIExtensions
{
- public static IServiceCollection AddData(this IServiceCollection services, IRepoSetupOptions options)
+ public static IServiceCollection AddData(this IServiceCollection services, IConfiguration configuration)
{
return services
.AddScoped()
@@ -20,17 +19,30 @@ public static class DIExtensions
.AddScoped(sp => sp.GetRequiredService())
.AddScoped(sp => sp.GetRequiredService())
.AddScoped(sp => sp.GetRequiredService())
- .AddSingleton(GetSetupService(options));
+ .AddSingleton(GetSetupService(configuration));
}
- private static IRepoSetupService GetSetupService(IRepoSetupOptions options)
+ private static IRepoSetupService GetSetupService(IConfiguration configuration)
{
- if (options is InMemoryDbSetupOptions inMemOptions)
- return new InMemorySetupService(inMemOptions);
+ var options = ReadConfigForOptions(configuration);
- if (options is SqliteDbSetupOptions sqliteOptions)
- return new SqliteSetupService(sqliteOptions);
+ return options switch
+ {
+ InMemoryDbSetupOptions inMemOptions => new InMemorySetupService(inMemOptions),
+ SqliteDbSetupOptions sqliteOptions => new SqliteSetupService(sqliteOptions),
+ _ => throw new ArgumentException("Options type not supported", nameof(configuration))
+ };
+ }
- throw new ArgumentException("Options type not supported", nameof(options));
+ private static IRepoSetupOptions? ReadConfigForOptions(IConfiguration configuration)
+ {
+ var dbConfig = configuration.GetSection("Database");
+ var dbType = dbConfig["Type"]?.ToLower();
+ return dbType switch
+ {
+ "inmemory" => dbConfig.Get(),
+ "sqlite" => dbConfig.Get(),
+ _ => null
+ };
}
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Data/Migrations/20260622195820_InitialCreate.Designer.cs b/backend/libs/Solicitors.Data/Migrations/20260622195820_InitialCreate.Designer.cs
deleted file mode 100644
index 0b8622e..0000000
--- a/backend/libs/Solicitors.Data/Migrations/20260622195820_InitialCreate.Designer.cs
+++ /dev/null
@@ -1,222 +0,0 @@
-//
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Solicitors.Data;
-
-#nullable disable
-
-namespace Solicitors.Data.Migrations
-{
- [DbContext(typeof(SolicitorsRepository))]
- [Migration("20260622195820_InitialCreate")]
- partial class InitialCreate
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder.HasAnnotation("ProductVersion", "10.0.9");
-
- modelBuilder.Entity("CitySolicitor", b =>
- {
- b.Property("CitiesCityId")
- .HasColumnType("TEXT");
-
- b.Property("SolicitorsSolicitorId")
- .HasColumnType("TEXT");
-
- b.HasKey("CitiesCityId", "SolicitorsSolicitorId");
-
- b.HasIndex("SolicitorsSolicitorId");
-
- b.ToTable("CitySolicitor");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.City", b =>
- {
- b.Property("CityId")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Name")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.HasKey("CityId");
-
- b.ToTable("Cities");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.Solicitor", b =>
- {
- b.Property("SolicitorId")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Email")
- .HasColumnType("TEXT");
-
- b.Property("Name")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("Phone")
- .HasColumnType("TEXT");
-
- b.Property("RelativeUrl")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("ShortDescription")
- .HasColumnType("TEXT");
-
- b.Property("Website")
- .HasColumnType("TEXT");
-
- b.HasKey("SolicitorId");
-
- b.ToTable("Solicitors");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorLocation", b =>
- {
- b.Property("SolicitorLocationId")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Address")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("Phone")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("SolicitorId")
- .HasColumnType("TEXT");
-
- b.HasKey("SolicitorLocationId");
-
- b.HasIndex("SolicitorId");
-
- b.ToTable("SolicitorLocations");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorLocationRating", b =>
- {
- b.Property("SolicitorLocationRatingId")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Maximum")
- .HasColumnType("TEXT");
-
- b.Property("Provider")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("SolicitorLocationId")
- .HasColumnType("TEXT");
-
- b.Property("Value")
- .HasColumnType("TEXT");
-
- b.HasKey("SolicitorLocationRatingId");
-
- b.HasIndex("SolicitorLocationId");
-
- b.ToTable("SolicitorLocationRatings");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorRating", b =>
- {
- b.Property("SolicitorRatingId")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Maximum")
- .HasColumnType("TEXT");
-
- b.Property("Provider")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("SolicitorId")
- .HasColumnType("TEXT");
-
- b.Property("Value")
- .HasColumnType("TEXT");
-
- b.HasKey("SolicitorRatingId");
-
- b.HasIndex("SolicitorId");
-
- b.ToTable("SolicitorRatings");
- });
-
- modelBuilder.Entity("CitySolicitor", b =>
- {
- b.HasOne("Solicitors.Core.Models.City", null)
- .WithMany()
- .HasForeignKey("CitiesCityId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.HasOne("Solicitors.Core.Models.Solicitor", null)
- .WithMany()
- .HasForeignKey("SolicitorsSolicitorId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorLocation", b =>
- {
- b.HasOne("Solicitors.Core.Models.Solicitor", "Solicitor")
- .WithMany("Locations")
- .HasForeignKey("SolicitorId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Solicitor");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorLocationRating", b =>
- {
- b.HasOne("Solicitors.Core.Models.SolicitorLocation", "SolicitorLocation")
- .WithMany("LocationRatings")
- .HasForeignKey("SolicitorLocationId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("SolicitorLocation");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorRating", b =>
- {
- b.HasOne("Solicitors.Core.Models.Solicitor", "Solicitor")
- .WithMany("Ratings")
- .HasForeignKey("SolicitorId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Solicitor");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.Solicitor", b =>
- {
- b.Navigation("Locations");
-
- b.Navigation("Ratings");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorLocation", b =>
- {
- b.Navigation("LocationRatings");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/backend/libs/Solicitors.Data/Migrations/20260622195820_InitialCreate.cs b/backend/libs/Solicitors.Data/Migrations/20260622195820_InitialCreate.cs
deleted file mode 100644
index 708be18..0000000
--- a/backend/libs/Solicitors.Data/Migrations/20260622195820_InitialCreate.cs
+++ /dev/null
@@ -1,172 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace Solicitors.Data.Migrations
-{
- ///
- public partial class InitialCreate : Migration
- {
- ///
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.CreateTable(
- name: "Cities",
- columns: table => new
- {
- CityId = table.Column(type: "TEXT", nullable: false),
- Name = table.Column(type: "TEXT", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Cities", x => x.CityId);
- });
-
- migrationBuilder.CreateTable(
- name: "Solicitors",
- columns: table => new
- {
- SolicitorId = table.Column(type: "TEXT", nullable: false),
- Name = table.Column(type: "TEXT", nullable: false),
- RelativeUrl = table.Column(type: "TEXT", nullable: false),
- Phone = table.Column(type: "TEXT", nullable: true),
- Email = table.Column(type: "TEXT", nullable: true),
- Website = table.Column(type: "TEXT", nullable: true),
- ShortDescription = table.Column(type: "TEXT", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Solicitors", x => x.SolicitorId);
- });
-
- migrationBuilder.CreateTable(
- name: "CitySolicitor",
- columns: table => new
- {
- CitiesCityId = table.Column(type: "TEXT", nullable: false),
- SolicitorsSolicitorId = table.Column(type: "TEXT", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_CitySolicitor", x => new { x.CitiesCityId, x.SolicitorsSolicitorId });
- table.ForeignKey(
- name: "FK_CitySolicitor_Cities_CitiesCityId",
- column: x => x.CitiesCityId,
- principalTable: "Cities",
- principalColumn: "CityId",
- onDelete: ReferentialAction.Cascade);
- table.ForeignKey(
- name: "FK_CitySolicitor_Solicitors_SolicitorsSolicitorId",
- column: x => x.SolicitorsSolicitorId,
- principalTable: "Solicitors",
- principalColumn: "SolicitorId",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "SolicitorLocations",
- columns: table => new
- {
- SolicitorLocationId = table.Column(type: "TEXT", nullable: false),
- Address = table.Column(type: "TEXT", nullable: false),
- Phone = table.Column(type: "TEXT", nullable: false),
- SolicitorId = table.Column(type: "TEXT", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_SolicitorLocations", x => x.SolicitorLocationId);
- table.ForeignKey(
- name: "FK_SolicitorLocations_Solicitors_SolicitorId",
- column: x => x.SolicitorId,
- principalTable: "Solicitors",
- principalColumn: "SolicitorId",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "SolicitorRatings",
- columns: table => new
- {
- SolicitorRatingId = table.Column(type: "TEXT", nullable: false),
- Value = table.Column(type: "TEXT", nullable: false),
- Maximum = table.Column(type: "TEXT", nullable: false),
- Provider = table.Column(type: "TEXT", nullable: false),
- SolicitorId = table.Column(type: "TEXT", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_SolicitorRatings", x => x.SolicitorRatingId);
- table.ForeignKey(
- name: "FK_SolicitorRatings_Solicitors_SolicitorId",
- column: x => x.SolicitorId,
- principalTable: "Solicitors",
- principalColumn: "SolicitorId",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "SolicitorLocationRatings",
- columns: table => new
- {
- SolicitorLocationRatingId = table.Column(type: "TEXT", nullable: false),
- Value = table.Column(type: "TEXT", nullable: false),
- Maximum = table.Column(type: "TEXT", nullable: false),
- Provider = table.Column(type: "TEXT", nullable: false),
- SolicitorLocationId = table.Column(type: "TEXT", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_SolicitorLocationRatings", x => x.SolicitorLocationRatingId);
- table.ForeignKey(
- name: "FK_SolicitorLocationRatings_SolicitorLocations_SolicitorLocationId",
- column: x => x.SolicitorLocationId,
- principalTable: "SolicitorLocations",
- principalColumn: "SolicitorLocationId",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateIndex(
- name: "IX_CitySolicitor_SolicitorsSolicitorId",
- table: "CitySolicitor",
- column: "SolicitorsSolicitorId");
-
- migrationBuilder.CreateIndex(
- name: "IX_SolicitorLocationRatings_SolicitorLocationId",
- table: "SolicitorLocationRatings",
- column: "SolicitorLocationId");
-
- migrationBuilder.CreateIndex(
- name: "IX_SolicitorLocations_SolicitorId",
- table: "SolicitorLocations",
- column: "SolicitorId");
-
- migrationBuilder.CreateIndex(
- name: "IX_SolicitorRatings_SolicitorId",
- table: "SolicitorRatings",
- column: "SolicitorId");
- }
-
- ///
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropTable(
- name: "CitySolicitor");
-
- migrationBuilder.DropTable(
- name: "SolicitorLocationRatings");
-
- migrationBuilder.DropTable(
- name: "SolicitorRatings");
-
- migrationBuilder.DropTable(
- name: "Cities");
-
- migrationBuilder.DropTable(
- name: "SolicitorLocations");
-
- migrationBuilder.DropTable(
- name: "Solicitors");
- }
- }
-}
diff --git a/backend/libs/Solicitors.Data/Migrations/SolicitorsRepositoryModelSnapshot.cs b/backend/libs/Solicitors.Data/Migrations/SolicitorsRepositoryModelSnapshot.cs
deleted file mode 100644
index 3552058..0000000
--- a/backend/libs/Solicitors.Data/Migrations/SolicitorsRepositoryModelSnapshot.cs
+++ /dev/null
@@ -1,219 +0,0 @@
-//
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Solicitors.Data;
-
-#nullable disable
-
-namespace Solicitors.Data.Migrations
-{
- [DbContext(typeof(SolicitorsRepository))]
- partial class SolicitorsRepositoryModelSnapshot : ModelSnapshot
- {
- protected override void BuildModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder.HasAnnotation("ProductVersion", "10.0.9");
-
- modelBuilder.Entity("CitySolicitor", b =>
- {
- b.Property("CitiesCityId")
- .HasColumnType("TEXT");
-
- b.Property("SolicitorsSolicitorId")
- .HasColumnType("TEXT");
-
- b.HasKey("CitiesCityId", "SolicitorsSolicitorId");
-
- b.HasIndex("SolicitorsSolicitorId");
-
- b.ToTable("CitySolicitor");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.City", b =>
- {
- b.Property("CityId")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Name")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.HasKey("CityId");
-
- b.ToTable("Cities");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.Solicitor", b =>
- {
- b.Property("SolicitorId")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Email")
- .HasColumnType("TEXT");
-
- b.Property("Name")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("Phone")
- .HasColumnType("TEXT");
-
- b.Property("RelativeUrl")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("ShortDescription")
- .HasColumnType("TEXT");
-
- b.Property("Website")
- .HasColumnType("TEXT");
-
- b.HasKey("SolicitorId");
-
- b.ToTable("Solicitors");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorLocation", b =>
- {
- b.Property("SolicitorLocationId")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Address")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("Phone")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("SolicitorId")
- .HasColumnType("TEXT");
-
- b.HasKey("SolicitorLocationId");
-
- b.HasIndex("SolicitorId");
-
- b.ToTable("SolicitorLocations");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorLocationRating", b =>
- {
- b.Property("SolicitorLocationRatingId")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Maximum")
- .HasColumnType("TEXT");
-
- b.Property("Provider")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("SolicitorLocationId")
- .HasColumnType("TEXT");
-
- b.Property("Value")
- .HasColumnType("TEXT");
-
- b.HasKey("SolicitorLocationRatingId");
-
- b.HasIndex("SolicitorLocationId");
-
- b.ToTable("SolicitorLocationRatings");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorRating", b =>
- {
- b.Property("SolicitorRatingId")
- .ValueGeneratedOnAdd()
- .HasColumnType("TEXT");
-
- b.Property("Maximum")
- .HasColumnType("TEXT");
-
- b.Property("Provider")
- .IsRequired()
- .HasColumnType("TEXT");
-
- b.Property("SolicitorId")
- .HasColumnType("TEXT");
-
- b.Property("Value")
- .HasColumnType("TEXT");
-
- b.HasKey("SolicitorRatingId");
-
- b.HasIndex("SolicitorId");
-
- b.ToTable("SolicitorRatings");
- });
-
- modelBuilder.Entity("CitySolicitor", b =>
- {
- b.HasOne("Solicitors.Core.Models.City", null)
- .WithMany()
- .HasForeignKey("CitiesCityId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.HasOne("Solicitors.Core.Models.Solicitor", null)
- .WithMany()
- .HasForeignKey("SolicitorsSolicitorId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorLocation", b =>
- {
- b.HasOne("Solicitors.Core.Models.Solicitor", "Solicitor")
- .WithMany("Locations")
- .HasForeignKey("SolicitorId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Solicitor");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorLocationRating", b =>
- {
- b.HasOne("Solicitors.Core.Models.SolicitorLocation", "SolicitorLocation")
- .WithMany("LocationRatings")
- .HasForeignKey("SolicitorLocationId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("SolicitorLocation");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorRating", b =>
- {
- b.HasOne("Solicitors.Core.Models.Solicitor", "Solicitor")
- .WithMany("Ratings")
- .HasForeignKey("SolicitorId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Solicitor");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.Solicitor", b =>
- {
- b.Navigation("Locations");
-
- b.Navigation("Ratings");
- });
-
- modelBuilder.Entity("Solicitors.Core.Models.SolicitorLocation", b =>
- {
- b.Navigation("LocationRatings");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/backend/libs/Solicitors.Data/RepositorySetup/IRepoSetupOptions.cs b/backend/libs/Solicitors.Data/RepositorySetup/IRepoSetupOptions.cs
index 6a0c460..0077099 100644
--- a/backend/libs/Solicitors.Data/RepositorySetup/IRepoSetupOptions.cs
+++ b/backend/libs/Solicitors.Data/RepositorySetup/IRepoSetupOptions.cs
@@ -1,6 +1,3 @@
namespace Solicitors.Data.RepositorySetup;
-public interface IRepoSetupOptions
-{
-
-}
\ No newline at end of file
+public interface IRepoSetupOptions;
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteDbSetupOptions.cs b/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteDbSetupOptions.cs
index 7bc1f54..205410c 100644
--- a/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteDbSetupOptions.cs
+++ b/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteDbSetupOptions.cs
@@ -1,6 +1,6 @@
namespace Solicitors.Data.RepositorySetup.Sqlite;
-public class SqliteDbSetupOptions(string dbName) : IRepoSetupOptions
+public class SqliteDbSetupOptions(string dbPath) : IRepoSetupOptions
{
- public string DbName { get; } = dbName;
+ public string DbPath { get; } = dbPath;
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteSetupService.cs b/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteSetupService.cs
index 2568541..d2b6a76 100644
--- a/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteSetupService.cs
+++ b/backend/libs/Solicitors.Data/RepositorySetup/Sqlite/SqliteSetupService.cs
@@ -6,11 +6,6 @@ internal class SqliteSetupService(SqliteDbSetupOptions config) : IRepoSetupServi
{
public void OnConfiguring(DbContextOptionsBuilder options)
{
- var folder = Environment.SpecialFolder.MyDocuments;
- var path = Environment.GetFolderPath(folder);
- path = Path.Combine(path, "/data");
- var dbPath = System.IO.Path.Join(path, $"{config.DbName}.db");
- var dbString = $"Data Source={dbPath}";
- options.UseSqlite(dbString);
+ options.UseSqlite($"Data Source={config.DbPath}");
}
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.Data/Solicitors.Data.csproj b/backend/libs/Solicitors.Data/Solicitors.Data.csproj
index 5685344..cec2d80 100644
--- a/backend/libs/Solicitors.Data/Solicitors.Data.csproj
+++ b/backend/libs/Solicitors.Data/Solicitors.Data.csproj
@@ -14,6 +14,7 @@
+
diff --git a/backend/libs/Solicitors.Data/SolicitorsRepository.cs b/backend/libs/Solicitors.Data/SolicitorsRepository.cs
index de69605..3fd6ab1 100644
--- a/backend/libs/Solicitors.Data/SolicitorsRepository.cs
+++ b/backend/libs/Solicitors.Data/SolicitorsRepository.cs
@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
-using Solicitors.Core;
using Solicitors.Core.Data;
using Solicitors.Core.Models;
using Solicitors.Core.Models.Imports;
@@ -7,15 +6,9 @@ using Solicitors.Data.RepositorySetup;
namespace Solicitors.Data;
-internal class SolicitorsRepository : DbContext, ISolicitorRepository, IReadOnlyCitiesRepository, IRatingsProviderRepository
+internal class SolicitorsRepository(IRepoSetupService setup)
+ : DbContext, ISolicitorRepository, IReadOnlyCitiesRepository, IRatingsProviderRepository
{
- private readonly IRepoSetupService _setup;
-
- public SolicitorsRepository(IRepoSetupService setup)
- {
- _setup = setup;
- }
-
public DbSet Solicitors { get; set; }
public DbSet SolicitorRatings { get; set; }
public DbSet SolicitorLocations { get; set; }
@@ -23,7 +16,10 @@ internal class SolicitorsRepository : DbContext, ISolicitorRepository, IReadOnly
public DbSet Cities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- => _setup.OnConfiguring(optionsBuilder);
+ => setup.OnConfiguring(optionsBuilder);
+
+ public Task EnsureCreatedAsync(CancellationToken cancellationToken)
+ => Database.EnsureCreatedAsync(cancellationToken);
public IAsyncEnumerable GetAllSolicitorsAsync()
=> Solicitors
@@ -41,104 +37,7 @@ internal class SolicitorsRepository : DbContext, ISolicitorRepository, IReadOnly
.Include(solicitor => solicitor.Ratings)
.FirstOrDefaultAsync(x => x.SolicitorId == id, cancellationToken);
- public async Task AddOrUpdateSolicitorAsync(SolicitorData solicitor, CancellationToken cancellationToken)
- {
- var matchingSolicitor = await Solicitors
- .FirstOrDefaultAsync(
- item => item.Name == solicitor.Name,
- cancellationToken: cancellationToken);
-
- if (matchingSolicitor is null)
- await AddNewSolicitor(solicitor, cancellationToken);
- else
- await UpdateExistingSolicitor(solicitor, matchingSolicitor, cancellationToken);
- }
-
- private async Task AddNewSolicitor(
- SolicitorData solicitor,
- CancellationToken cancellationToken)
- {
- var newSol = new Solicitor()
- {
- Name = solicitor.Name,
- RelativeUrl = solicitor.UrlPath,
- Phone = solicitor.Phone,
- Email = solicitor.Email,
- Website = solicitor.Website,
- ShortDescription = solicitor.ShortDescription
- };
- Solicitors.Add(newSol);
-
- var cityTasks = solicitor.Cities
- .Distinct()
- .Select(async (city) =>
- {
- var matchingCity = await Cities.FirstOrDefaultAsync(
- item => item.Name == city,
- cancellationToken: cancellationToken);
-
- if (matchingCity is null)
- {
- matchingCity = new City() { Name = city };
- Cities.Add(matchingCity);
- newSol.Cities.Add(matchingCity);
- }
-
- matchingCity.Solicitors.Add(newSol);
- });
- await Task.WhenAll(cityTasks);
-
- foreach (var rating in solicitor.Ratings)
- {
- var newRating = new SolicitorRating()
- {
- Value = rating.Value,
- Maximum = rating.MaxValue,
- Provider = rating.RatingProvider,
- Solicitor = newSol
- };
- SolicitorRatings.Add(newRating);
- newSol.Ratings.Add(newRating);
- }
-
- foreach (var location in solicitor.Offices)
- {
- var newLocation = new SolicitorLocation()
- {
- Address = location.Address,
- Phone = location.Phone,
- Solicitor = newSol
- };
- SolicitorLocations.Add(newLocation);
- newSol.Locations.Add(newLocation);
-
- foreach (var rating in location.LocationRatings)
- {
- var newLocRating = new SolicitorLocationRating()
- {
- Value = rating.value,
- Maximum = rating.MaxValue,
- Provider = rating.RatingProvider,
- SolicitorLocation = newLocation
- };
- SolicitorLocationRatings.Add(newLocRating);
- newLocation.LocationRatings.Add(newLocRating);
- }
- }
-
- await SaveChangesAsync(cancellationToken);
- }
-
- private Task UpdateExistingSolicitor(
- SolicitorData solicitor,
- Solicitor matchingSolicitor,
- CancellationToken cancellationToken)
- {
- // TODO
- return Task.CompletedTask;
- }
-
- public IAsyncEnumerable GetCitiesAsync(CancellationToken cancellationToken)
+ public IAsyncEnumerable GetCitiesAsync()
{
return Cities
.Select(city => city.Name)
@@ -154,4 +53,286 @@ internal class SolicitorsRepository : DbContext, ISolicitorRepository, IReadOnly
.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 GetCityByNameAsync(string cityName, CancellationToken cancellationToken)
+ {
+ return Cities
+ .Include(city => city.Solicitors)
+ .FirstOrDefaultAsync(
+ city => city.Name == cityName,
+ cancellationToken: cancellationToken);
+ }
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.HtmlParsing/DIExtensions.cs b/backend/libs/Solicitors.HtmlParsing/DIExtensions.cs
index 286b841..b12689a 100644
--- a/backend/libs/Solicitors.HtmlParsing/DIExtensions.cs
+++ b/backend/libs/Solicitors.HtmlParsing/DIExtensions.cs
@@ -1,7 +1,9 @@
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
namespace Solicitors.HtmlParsing;
+[SuppressMessage("ReSharper", "InconsistentNaming")]
public static class DIExtensions
{
public static IServiceCollection AddHtmlParsing(this IServiceCollection services)
diff --git a/backend/libs/Solicitors.HtmlParsing/HtmlParser.cs b/backend/libs/Solicitors.HtmlParsing/HtmlParser.cs
index 2cbd4cf..7b45a5b 100644
--- a/backend/libs/Solicitors.HtmlParsing/HtmlParser.cs
+++ b/backend/libs/Solicitors.HtmlParsing/HtmlParser.cs
@@ -33,7 +33,7 @@ internal class HtmlParser : IHtmlParser
return ParseHtml(tokenEnumerator);
}
- private IEnumerable LexHtml(IEnumerator content)
+ private IEnumerable LexHtml(CharEnumerator content)
{
var work = "";
while (content.MoveNext())
@@ -85,14 +85,14 @@ internal class HtmlParser : IHtmlParser
}
}
- bool IsVoidOrSelfClosingElement(string token)
+ private bool IsVoidOrSelfClosingElement(string token)
{
if (token.StartsWith(""))
return true;
- var tagName = token.Split(' ').First().Substring(1);
+ var tagName = token.Split(' ').First()[1..];
if (tagName.EndsWith('>'))
- tagName = tagName.Substring(0, tagName.Length - 1);
+ tagName = tagName[..^1];
return _voidElements.Contains(tagName);
}
@@ -100,8 +100,8 @@ internal class HtmlParser : IHtmlParser
{
var attributesString = string.Join(' ', tokenNoBraces.Split(' ').Skip(1));
var attString = "";
- bool inQuote = false;
- foreach (char c in attributesString)
+ var inQuote = false;
+ foreach (var c in attributesString)
{
if (!inQuote && c == ' ')
{
diff --git a/backend/libs/Solicitors.HtmlParsing/Models/HtmlNode.cs b/backend/libs/Solicitors.HtmlParsing/Models/HtmlNode.cs
index 050e011..971014f 100644
--- a/backend/libs/Solicitors.HtmlParsing/Models/HtmlNode.cs
+++ b/backend/libs/Solicitors.HtmlParsing/Models/HtmlNode.cs
@@ -2,19 +2,16 @@ using System.Diagnostics.CodeAnalysis;
namespace Solicitors.HtmlParsing.Models;
-internal class HtmlNode(string tagName, IHtmlAttribute[] attributes)
+internal class HtmlNode(string name, IHtmlAttribute[] attributes)
: IHtmlNode
{
- private readonly IHtmlAttribute[] _attributes = attributes;
- private readonly string _tagName = tagName;
-
public virtual bool TryGetByTagName(
string tagName,
[NotNullWhen(true)] out IHtmlNode? node,
bool noChildren = false)
{
node = null;
- if (tagName == _tagName)
+ if (tagName == name)
node = this;
return node is not null;
@@ -24,7 +21,7 @@ internal class HtmlNode(string tagName, IHtmlAttribute[] attributes)
{
get
{
- var classAttribute = _attributes
+ var classAttribute = attributes
.Where(a => a is HtmlValueAttribute)
.Select(a => (a as HtmlValueAttribute)!)
.FirstOrDefault(a => a.Name.Equals("class", StringComparison.CurrentCultureIgnoreCase));
@@ -55,7 +52,7 @@ internal class HtmlNode(string tagName, IHtmlAttribute[] attributes)
bool noChildren = false)
{
node = null;
- if (tagName == _tagName && Classes.Contains(className, StringComparer.InvariantCultureIgnoreCase))
+ if (tagName == name && Classes.Contains(className, StringComparer.InvariantCultureIgnoreCase))
node = this;
return node is not null;
@@ -75,7 +72,7 @@ internal class HtmlNode(string tagName, IHtmlAttribute[] attributes)
public bool HasAttribute(string attributeName)
{
- return _attributes
+ return attributes
.Any(a => a.Name.Equals(attributeName, StringComparison.InvariantCultureIgnoreCase));
}
@@ -83,7 +80,7 @@ internal class HtmlNode(string tagName, IHtmlAttribute[] attributes)
{
attributeValue = null;
- var matchingAttribute = _attributes.FirstOrDefault(
+ var matchingAttribute = attributes.FirstOrDefault(
a => a.Name.Equals(attributeName, StringComparison.InvariantCultureIgnoreCase)
);
diff --git a/backend/libs/Solicitors.HtmlParsing/Models/ParentHtmlNode.cs b/backend/libs/Solicitors.HtmlParsing/Models/ParentHtmlNode.cs
index 61a0eb7..ae229b1 100644
--- a/backend/libs/Solicitors.HtmlParsing/Models/ParentHtmlNode.cs
+++ b/backend/libs/Solicitors.HtmlParsing/Models/ParentHtmlNode.cs
@@ -5,8 +5,6 @@ namespace Solicitors.HtmlParsing.Models;
internal class ParentHtmlNode(string tagName, IHtmlAttribute[] attributes, IHtmlNode[] children)
: HtmlNode(tagName, attributes)
{
- private readonly IHtmlNode[] _children = children;
-
public override bool TryGetByTagName(
string tagName,
[NotNullWhen(true)] out IHtmlNode? node,
@@ -15,7 +13,7 @@ internal class ParentHtmlNode(string tagName, IHtmlAttribute[] attributes, IHtml
node = null;
if (!base.TryGetByTagName(tagName, out node, noChildren) && !noChildren)
{
- var nodesToCheck = new Queue(_children);
+ var nodesToCheck = new Queue(children);
while (nodesToCheck.TryDequeue(out var child))
{
if (child.TryGetByTagName(tagName, out var childNode, true))
@@ -41,7 +39,7 @@ internal class ParentHtmlNode(string tagName, IHtmlAttribute[] attributes, IHtml
node = null;
if (!base.TryGetByClass(className, out node, noChildren) && !noChildren)
{
- var nodesToCheck = new Queue(_children);
+ var nodesToCheck = new Queue(children);
while (nodesToCheck.TryDequeue(out var child))
{
if (child.TryGetByClass(className, out var childNode, true))
@@ -68,7 +66,7 @@ internal class ParentHtmlNode(string tagName, IHtmlAttribute[] attributes, IHtml
node = null;
if (!base.TryGetByTagNameAndClass(tagName, className, out node, noChildren) && !noChildren)
{
- var nodesToCheck = new Queue(_children);
+ var nodesToCheck = new Queue(children);
while (nodesToCheck.TryDequeue(out var child))
{
if (child.TryGetByTagNameAndClass(tagName, className, out var childNode, true))
@@ -86,9 +84,9 @@ internal class ParentHtmlNode(string tagName, IHtmlAttribute[] attributes, IHtml
return node is not null;
}
- public override bool TryGetChildren([NotNullWhen(true)] out IEnumerable? children)
+ public override bool TryGetChildren([NotNullWhen(true)] out IEnumerable? children1)
{
- children = _children;
- return _children.Length != 0;
+ children1 = children;
+ return children.Length != 0;
}
}
\ No newline at end of file
diff --git a/backend/libs/Solicitors.HtmlParsing/Models/StringNode.cs b/backend/libs/Solicitors.HtmlParsing/Models/StringNode.cs
index 4669fb7..6b2009c 100644
--- a/backend/libs/Solicitors.HtmlParsing/Models/StringNode.cs
+++ b/backend/libs/Solicitors.HtmlParsing/Models/StringNode.cs
@@ -5,8 +5,6 @@ namespace Solicitors.HtmlParsing.Models;
internal class StringNode(string content)
: IHtmlNode
{
- private readonly string _content = content;
-
public bool TryGetByTagName(
string tagName,
[NotNullWhen(true)] out IHtmlNode? node,
@@ -37,7 +35,7 @@ internal class StringNode(string content)
public bool TryGetText([NotNullWhen(true)] out string? text)
{
- text = _content;
+ text = content;
return !string.IsNullOrEmpty(text);
}
diff --git a/frontend/src/functions/getSolicitor.ts b/frontend/src/functions/getSolicitor.ts
index 382843a..6c32363 100644
--- a/frontend/src/functions/getSolicitor.ts
+++ b/frontend/src/functions/getSolicitor.ts
@@ -2,6 +2,6 @@ import { SolicitorInfo } from "../models/solicitorSummary";
import api from "./axios";
export default async function GetSolicitor(id : string) : Promise {
- const response = await api.get(`/conveyancors/${id}`);
+ const response = await api.get(`/solicitors/${id}`);
return response.data as SolicitorInfo;
}
\ No newline at end of file
diff --git a/frontend/src/functions/getSolicitors.ts b/frontend/src/functions/getSolicitors.ts
index c69a5a4..439b809 100644
--- a/frontend/src/functions/getSolicitors.ts
+++ b/frontend/src/functions/getSolicitors.ts
@@ -17,6 +17,6 @@ export default async function GetSolicitors(filters : FilterState) {
if (filters.sortBy != null) {
queryString += `&orderingType=${filters.sortBy}`;
}
- const response = await api.get(`/conveyancors?${queryString}`);
+ const response = await api.get(`/solicitors?${queryString}`);
return response.data as PaginationResponse;
}
\ No newline at end of file