mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 01:54:43 +00:00
Integrate Spectre.Console into seed application, use async data loading
This commit is contained in:
@@ -11,6 +11,8 @@
|
|||||||
<PackageReference Include="idunno.Password.Generator" Version="2.0.0" />
|
<PackageReference Include="idunno.Password.Generator" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.2" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.2" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.0" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Spectre.Console" Version="0.57.2" />
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
remove this override once microsoft ships official fix to vulnerability
|
remove this override once microsoft ships official fix to vulnerability
|
||||||
@@ -24,14 +26,12 @@
|
|||||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
|
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
|
||||||
<ProjectReference Include="..\..\Features\Features.Breweries\Features.Breweries.csproj" />
|
<ProjectReference Include="..\..\Features\Features.Breweries\Features.Breweries.csproj" />
|
||||||
<ProjectReference Include="..\..\Features\Features.Auth\Features.Auth.csproj" />
|
<ProjectReference Include="..\..\Features\Features.Auth\Features.Auth.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Features\Features.Locations\Features.Locations.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- Pipeline-generated seed data, copied to the output directory so it can be opened via
|
<!-- Pipeline-generated seed data, copied to the output directory so it can be opened via
|
||||||
ConnectionStrings.SqliteConnectionString at runtime. -->
|
ConnectionStrings.SqliteConnectionString at runtime. -->
|
||||||
<None
|
<None CopyToOutputDirectory="PreserveNewest" Include="SeedData\biergarten_seed_2026-07-20T05-46-02.993511Z.sqlite" />
|
||||||
CopyToOutputDirectory="PreserveNewest"
|
|
||||||
Include="SeedData\biergarten_seed_2026-07-20T05-46-02.993511Z.sqlite"
|
|
||||||
/>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -7,112 +7,243 @@ using Features.Auth.Dtos;
|
|||||||
using Features.Auth.Repository;
|
using Features.Auth.Repository;
|
||||||
using Features.Breweries.DependencyInjection;
|
using Features.Breweries.DependencyInjection;
|
||||||
using Features.Breweries.Repository;
|
using Features.Breweries.Repository;
|
||||||
|
using Features.Locations.DependencyInjection;
|
||||||
|
using Features.Locations.Dtos;
|
||||||
|
using Features.Locations.Repository;
|
||||||
using idunno.Password;
|
using idunno.Password;
|
||||||
using Infrastructure.PasswordHashing;
|
using Infrastructure.PasswordHashing;
|
||||||
using Infrastructure.Sql;
|
using Infrastructure.Sql;
|
||||||
using Microsoft.Data.Sqlite;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
static async Task<int> main()
|
return await RunAsync();
|
||||||
|
|
||||||
|
static async Task<int> RunAsync()
|
||||||
{
|
{
|
||||||
IReadOnlyList<BreweryRecord> breweries;
|
|
||||||
IReadOnlyList<UserRecord> users;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
ServiceCollection services = [];
|
||||||
|
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
|
||||||
|
services.AddSingleton<ISqlConnectionFactory, DefaultSqlConnectionFactory>();
|
||||||
|
services.AddFeaturesBreweries();
|
||||||
|
services.AddFeaturesLocations();
|
||||||
|
services.AddFeaturesAuth();
|
||||||
|
|
||||||
|
await using ServiceProvider provider = services.BuildServiceProvider();
|
||||||
|
|
||||||
|
IBreweryRepository breweryRepository = provider.GetRequiredService<IBreweryRepository>();
|
||||||
|
ILocationRepository locationRepository = provider.GetRequiredService<ILocationRepository>();
|
||||||
|
IAuthRepository authRepository = provider.GetRequiredService<IAuthRepository>();
|
||||||
|
IPasswordInfrastructure passwordInfrastructure =
|
||||||
|
provider.GetRequiredService<IPasswordInfrastructure>();
|
||||||
|
|
||||||
|
AnsiConsole.Write(new Rule("[bold green]Database Seeder[/]").LeftJustified());
|
||||||
|
AnsiConsole.MarkupLine("[grey]Connecting to SQLite source and loading seed data...[/]");
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
|
||||||
PipelineSeedDataReader reader = new(ConnectionStrings.SqliteConnectionString);
|
PipelineSeedDataReader reader = new(ConnectionStrings.SqliteConnectionString);
|
||||||
breweries = reader.ReadBreweryRecords();
|
|
||||||
users = reader.ReadUserRecords();
|
SeedData seedData = null!;
|
||||||
|
IReadOnlyList<Guid> postedByIds = [];
|
||||||
|
|
||||||
|
await AnsiConsole
|
||||||
|
.Status()
|
||||||
|
.Spinner(Spinner.Known.Dots)
|
||||||
|
.SpinnerStyle(Style.Parse("green"))
|
||||||
|
.StartAsync(
|
||||||
|
"Loading seed data...",
|
||||||
|
async ctx =>
|
||||||
|
{
|
||||||
|
seedData = await reader.ReadSeedDataAsync();
|
||||||
|
ctx.Status(
|
||||||
|
$"Loaded {seedData.Breweries.Count} breweries and {seedData.Users.Count} users."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLine(
|
||||||
|
$"[green]✓[/] Loaded [bold]{seedData.Breweries.Count}[/] breweries."
|
||||||
|
);
|
||||||
|
AnsiConsole.Write(BuildBreweryTable(seedData.Breweries));
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLine($"[green]✓[/] Loaded [bold]{seedData.Users.Count}[/] users.");
|
||||||
|
AnsiConsole.Write(BuildUserTable(seedData.Users));
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
|
||||||
|
AnsiConsole.WriteLine("Seed data loaded successfully.");
|
||||||
|
|
||||||
|
AnsiConsole.Write(
|
||||||
|
new Rule("[bold green]Loading seed data into target database.[/]").LeftJustified()
|
||||||
|
);
|
||||||
|
|
||||||
|
await AnsiConsole
|
||||||
|
.Status()
|
||||||
|
.Spinner(Spinner.Known.Dots)
|
||||||
|
.SpinnerStyle(Style.Parse("green"))
|
||||||
|
.StartAsync(
|
||||||
|
"Loading user data into target database...",
|
||||||
|
async ctx =>
|
||||||
|
{
|
||||||
|
Thread.Sleep(3000); // Simulate some work being done
|
||||||
|
postedByIds = await LoadUsersIntoDatabaseAsync(
|
||||||
|
authRepository,
|
||||||
|
passwordInfrastructure,
|
||||||
|
seedData.Users
|
||||||
|
);
|
||||||
|
ctx.Status("User data loaded into target database.");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await AnsiConsole
|
||||||
|
.Status()
|
||||||
|
.Spinner(Spinner.Known.Dots)
|
||||||
|
.SpinnerStyle(Style.Parse("green"))
|
||||||
|
.StartAsync(
|
||||||
|
"Loading brewery data into target database...",
|
||||||
|
async ctx =>
|
||||||
|
{
|
||||||
|
await LoadBreweriesIntoDatabaseAsync(
|
||||||
|
breweryRepository,
|
||||||
|
locationRepository,
|
||||||
|
seedData.Breweries,
|
||||||
|
postedByIds
|
||||||
|
);
|
||||||
|
ctx.Status("Brewery data loaded into target database.");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
catch (SqliteException ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
AnsiConsole.MarkupLine("[red]Seeding failed.[/]");
|
||||||
Console.WriteLine($"Error opening database connection: {ex.Message}");
|
AnsiConsole.WriteException(ex);
|
||||||
Console.ResetColor();
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ServiceCollection services = [];
|
static async Task<IReadOnlyList<Guid>> LoadUsersIntoDatabaseAsync(
|
||||||
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
|
IAuthRepository authRepository,
|
||||||
services.AddSingleton<ISqlConnectionFactory, DefaultSqlConnectionFactory>();
|
IPasswordInfrastructure passwordInfrastructure,
|
||||||
services.AddFeaturesBreweries();
|
IReadOnlyList<UserRecord> users
|
||||||
services.AddFeaturesAuth();
|
)
|
||||||
|
{
|
||||||
|
List<Guid> userAccountIds = [];
|
||||||
|
|
||||||
using ServiceProvider provider = services.BuildServiceProvider();
|
foreach (UserRecord userRecord in users)
|
||||||
|
|
||||||
IBreweryRepository breweryRepository;
|
|
||||||
IAuthRepository authRepository;
|
|
||||||
IPasswordInfrastructure passwordInfrastructure;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
breweryRepository = provider.GetRequiredService<IBreweryRepository>();
|
UserAccount userAccount = await authRepository.RegisterUserAsync(
|
||||||
authRepository = provider.GetRequiredService<IAuthRepository>();
|
new UserRegistrationDto(
|
||||||
passwordInfrastructure = provider.GetRequiredService<IPasswordInfrastructure>();
|
userRecord.User.Username,
|
||||||
}
|
userRecord.User.FirstName,
|
||||||
catch (InvalidOperationException ex)
|
userRecord.User.LastName,
|
||||||
{
|
userRecord.Email,
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
DateTime.Parse(userRecord.DateOfBirth),
|
||||||
Console.WriteLine($"Error configuring database connection: {ex.Message}");
|
passwordInfrastructure.Hash(
|
||||||
Console.ResetColor();
|
PasswordGenerator.Generate(length: 64, numberOfDigits: 10, numberOfSymbols: 10)
|
||||||
return 1;
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
userAccountIds.Add(userAccount.UserAccountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
return userAccountIds;
|
||||||
Console.WriteLine($"Loaded {breweries.Count} breweries.");
|
}
|
||||||
Console.ResetColor();
|
|
||||||
|
/// <summary>
|
||||||
|
/// Persists each <paramref name="breweries" /> record, resolving (and creating, if needed) its
|
||||||
|
/// Country/StateProvince/City chain via <paramref name="locationRepository" />. The pipeline
|
||||||
|
/// data has no street-level address or a specific user tied to each brewery, so a placeholder
|
||||||
|
/// address is used and <c>PostedById</c> is assigned round-robin from <paramref name="posterUserIds" />
|
||||||
|
/// to keep seeding deterministic across runs.
|
||||||
|
/// </summary>
|
||||||
|
static async Task LoadBreweriesIntoDatabaseAsync(
|
||||||
|
IBreweryRepository breweryRepository,
|
||||||
|
ILocationRepository locationRepository,
|
||||||
|
IReadOnlyList<BreweryRecord> breweries,
|
||||||
|
IReadOnlyList<Guid> posterUserIds
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (posterUserIds.Count == 0)
|
||||||
|
throw new InvalidOperationException("Cannot load breweries without any registered users.");
|
||||||
|
|
||||||
for (int i = 0; i < breweries.Count; i++)
|
for (int i = 0; i < breweries.Count; i++)
|
||||||
{
|
{
|
||||||
BreweryRecord brewery = breweries[i];
|
BreweryRecord breweryRecord = breweries[i];
|
||||||
Console.WriteLine(
|
|
||||||
$"{i + 1}:\t{brewery.Brewery.NameEn}\t({brewery.Address.City.CityName}, {brewery.Address.City.Country})"
|
Guid cityId = await locationRepository.GetOrCreateCityIdAsync(
|
||||||
|
new CityLocation(
|
||||||
|
breweryRecord.Address.City.CityName,
|
||||||
|
breweryRecord.Address.City.StateProvince,
|
||||||
|
breweryRecord.Address.City.Iso31662,
|
||||||
|
breweryRecord.Address.City.Country,
|
||||||
|
breweryRecord.Address.City.Iso31661
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await breweryRepository.CreateAsync(
|
||||||
|
new BreweryPost
|
||||||
|
{
|
||||||
|
BreweryName = breweryRecord.Brewery.NameEn,
|
||||||
|
Description = breweryRecord.Brewery.DescriptionEn,
|
||||||
|
PostedById = posterUserIds[i % posterUserIds.Count],
|
||||||
|
Location = new BreweryPostLocation
|
||||||
|
{
|
||||||
|
CityId = cityId,
|
||||||
|
AddressLine1 = "Address unavailable",
|
||||||
|
PostalCode = breweryRecord.Address.PostalCode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Table BuildBreweryTable(IReadOnlyList<BreweryRecord> breweries)
|
||||||
|
{
|
||||||
|
Table table = new Table()
|
||||||
|
.AddColumn("Brewery Name (EN)")
|
||||||
|
.AddColumn("Brewery Name (Local)")
|
||||||
|
.AddColumn("City")
|
||||||
|
.AddColumn("State/Province")
|
||||||
|
.AddColumn("Country")
|
||||||
|
.AddColumn("Postal Code");
|
||||||
|
|
||||||
|
foreach (BreweryRecord brewery in breweries)
|
||||||
|
{
|
||||||
|
table.AddRow(
|
||||||
|
brewery.Brewery.NameEn,
|
||||||
|
brewery.Brewery.NameLocal,
|
||||||
|
brewery.Address.City.CityName,
|
||||||
|
brewery.Address.City.StateProvince,
|
||||||
|
brewery.Address.City.Country,
|
||||||
|
brewery.Address.PostalCode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
return table;
|
||||||
Console.WriteLine($"Loaded {users.Count} users.");
|
}
|
||||||
Console.ResetColor();
|
|
||||||
|
static Table BuildUserTable(IReadOnlyList<UserRecord> users)
|
||||||
|
{
|
||||||
|
Table table = new Table()
|
||||||
|
.AddColumn("Username")
|
||||||
|
.AddColumn("First Name")
|
||||||
|
.AddColumn("Last Name")
|
||||||
|
.AddColumn("Email")
|
||||||
|
.AddColumn("Date of Birth");
|
||||||
|
|
||||||
foreach (UserRecord user in users)
|
foreach (UserRecord user in users)
|
||||||
{
|
{
|
||||||
string username = user.User.Username;
|
table.AddRow(
|
||||||
string firstName = user.User.FirstName;
|
user.User.Username,
|
||||||
string lastName = user.User.LastName;
|
user.User.FirstName,
|
||||||
string email = user.Email;
|
user.User.LastName,
|
||||||
DateTime dateOfBirth = DateTime.Parse(user.DateOfBirth);
|
user.Email,
|
||||||
|
user.DateOfBirth.ToString()
|
||||||
string generatedPassword = PasswordGenerator.Generate(
|
|
||||||
length: 64,
|
|
||||||
numberOfDigits: 10,
|
|
||||||
numberOfSymbols: 10
|
|
||||||
);
|
);
|
||||||
string hashedPassword = passwordInfrastructure.Hash(generatedPassword);
|
|
||||||
|
|
||||||
UserRegistrationDto registrationDto = new(
|
|
||||||
username,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
email,
|
|
||||||
dateOfBirth,
|
|
||||||
hashedPassword
|
|
||||||
);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
UserAccount createdUser = await authRepository.RegisterUserAsync(registrationDto);
|
|
||||||
Console.WriteLine(
|
|
||||||
$"Created user: {createdUser.Username} ({createdUser.UserAccountId})"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
|
||||||
Console.WriteLine($"Error creating user {username}: {ex.Message}");
|
|
||||||
Console.ResetColor();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await main();
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#region
|
|
||||||
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Database.Seed.PipelineData;
|
using Database.Seed.PipelineData;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
namespace Database.Seed.Sqlite;
|
namespace Database.Seed.Sqlite;
|
||||||
|
|
||||||
|
public sealed record SeedData(
|
||||||
|
IReadOnlyList<BreweryRecord> Breweries,
|
||||||
|
IReadOnlyList<UserRecord> Users
|
||||||
|
);
|
||||||
|
|
||||||
public sealed class PipelineSeedDataReader
|
public sealed class PipelineSeedDataReader
|
||||||
{
|
{
|
||||||
private readonly string _connectionString;
|
private readonly string _connectionString;
|
||||||
@@ -17,13 +18,40 @@ public sealed class PipelineSeedDataReader
|
|||||||
_connectionString = connectionString;
|
_connectionString = connectionString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<BreweryRecord> ReadBreweryRecords()
|
/// <summary>
|
||||||
|
/// Reads breweries and users in a single connection, loading the cities
|
||||||
|
/// lookup only once and sharing it between both reads.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<SeedData> ReadSeedDataAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
using SqliteConnection connection = new(_connectionString);
|
await using SqliteConnection connection = new(_connectionString);
|
||||||
connection.Open();
|
await connection.OpenAsync(cancellationToken);
|
||||||
|
|
||||||
IReadOnlyDictionary<long, City> cities = ReadCities(connection);
|
IReadOnlyDictionary<long, City> cities = await ReadCitiesAsync(
|
||||||
|
connection,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
IReadOnlyList<BreweryRecord> breweries = await ReadBreweryRecordsAsync(
|
||||||
|
connection,
|
||||||
|
cities,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
IReadOnlyList<UserRecord> users = await ReadUserRecordsAsync(
|
||||||
|
connection,
|
||||||
|
cities,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
|
return new SeedData(breweries, users);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IReadOnlyList<BreweryRecord>> ReadBreweryRecordsAsync(
|
||||||
|
SqliteConnection connection,
|
||||||
|
IReadOnlyDictionary<long, City> cities,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
|
{
|
||||||
const string sql = """
|
const string sql = """
|
||||||
SELECT b.name_en, b.description_en, b.name_local, b.description_local,
|
SELECT b.name_en, b.description_en, b.name_local, b.description_local,
|
||||||
ba.city_id, ba.postal_code
|
ba.city_id, ba.postal_code
|
||||||
@@ -31,25 +59,32 @@ public sealed class PipelineSeedDataReader
|
|||||||
JOIN brewery_addresses ba ON ba.brewery_id = b.id;
|
JOIN brewery_addresses ba ON ba.brewery_id = b.id;
|
||||||
""";
|
""";
|
||||||
|
|
||||||
using SqliteCommand command = new(sql, connection);
|
await using SqliteCommand command = new(sql, connection);
|
||||||
using SqliteDataReader reader = command.ExecuteReader();
|
await using SqliteDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||||
|
|
||||||
|
const int nameEnIndex = 0;
|
||||||
|
const int descriptionEnIndex = 1;
|
||||||
|
const int nameLocalIndex = 2;
|
||||||
|
const int descriptionLocalIndex = 3;
|
||||||
|
const int cityIdIndex = 4;
|
||||||
|
const int postalCodeIndex = 5;
|
||||||
|
|
||||||
List<BreweryRecord> records = [];
|
List<BreweryRecord> records = [];
|
||||||
while (reader.Read())
|
while (await reader.ReadAsync(cancellationToken))
|
||||||
records.Add(
|
records.Add(
|
||||||
new BreweryRecord
|
new BreweryRecord
|
||||||
{
|
{
|
||||||
Brewery = new BreweryResult
|
Brewery = new BreweryResult
|
||||||
{
|
{
|
||||||
NameEn = reader.GetString(0),
|
NameEn = reader.GetString(nameEnIndex),
|
||||||
DescriptionEn = reader.GetString(1),
|
DescriptionEn = reader.GetString(descriptionEnIndex),
|
||||||
NameLocal = reader.GetString(2),
|
NameLocal = reader.GetString(nameLocalIndex),
|
||||||
DescriptionLocal = reader.GetString(3),
|
DescriptionLocal = reader.GetString(descriptionLocalIndex),
|
||||||
},
|
},
|
||||||
Address = new BreweryAddress
|
Address = new BreweryAddress
|
||||||
{
|
{
|
||||||
City = cities[reader.GetInt64(4)],
|
City = cities[reader.GetInt64(cityIdIndex)],
|
||||||
PostalCode = reader.GetString(5),
|
PostalCode = reader.GetString(postalCodeIndex),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -57,13 +92,12 @@ public sealed class PipelineSeedDataReader
|
|||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<UserRecord> ReadUserRecords()
|
private static async Task<IReadOnlyList<UserRecord>> ReadUserRecordsAsync(
|
||||||
|
SqliteConnection connection,
|
||||||
|
IReadOnlyDictionary<long, City> cities,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
{
|
{
|
||||||
using SqliteConnection connection = new(_connectionString);
|
|
||||||
connection.Open();
|
|
||||||
|
|
||||||
IReadOnlyDictionary<long, City> cities = ReadCities(connection);
|
|
||||||
|
|
||||||
const string sql = """
|
const string sql = """
|
||||||
SELECT u.first_name, u.last_name, u.gender, u.username, u.bio,
|
SELECT u.first_name, u.last_name, u.gender, u.username, u.bio,
|
||||||
u.activity_weight, u.email, u.date_of_birth,
|
u.activity_weight, u.email, u.date_of_birth,
|
||||||
@@ -72,29 +106,40 @@ public sealed class PipelineSeedDataReader
|
|||||||
JOIN user_addresses ua ON ua.user_id = u.id;
|
JOIN user_addresses ua ON ua.user_id = u.id;
|
||||||
""";
|
""";
|
||||||
|
|
||||||
using SqliteCommand command = new(sql, connection);
|
await using SqliteCommand command = new(sql, connection);
|
||||||
using SqliteDataReader reader = command.ExecuteReader();
|
await using SqliteDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||||
|
|
||||||
|
const int firstNameIndex = 0;
|
||||||
|
const int lastNameIndex = 1;
|
||||||
|
const int genderIndex = 2;
|
||||||
|
const int usernameIndex = 3;
|
||||||
|
const int bioIndex = 4;
|
||||||
|
const int activityWeightIndex = 5;
|
||||||
|
const int emailIndex = 6;
|
||||||
|
const int dateOfBirthIndex = 7;
|
||||||
|
const int cityIdIndex = 8;
|
||||||
|
const int postalCodeIndex = 9;
|
||||||
|
|
||||||
List<UserRecord> records = [];
|
List<UserRecord> records = [];
|
||||||
while (reader.Read())
|
while (await reader.ReadAsync(cancellationToken))
|
||||||
records.Add(
|
records.Add(
|
||||||
new UserRecord
|
new UserRecord
|
||||||
{
|
{
|
||||||
User = new UserResult
|
User = new UserResult
|
||||||
{
|
{
|
||||||
FirstName = reader.GetString(0),
|
FirstName = reader.GetString(firstNameIndex),
|
||||||
LastName = reader.GetString(1),
|
LastName = reader.GetString(lastNameIndex),
|
||||||
Gender = reader.GetString(2),
|
Gender = reader.GetString(genderIndex),
|
||||||
Username = reader.GetString(3),
|
Username = reader.GetString(usernameIndex),
|
||||||
Bio = reader.GetString(4),
|
Bio = reader.GetString(bioIndex),
|
||||||
ActivityWeight = (float)reader.GetDouble(5),
|
ActivityWeight = (float)reader.GetDouble(activityWeightIndex),
|
||||||
},
|
},
|
||||||
Email = reader.GetString(6),
|
Email = reader.GetString(emailIndex),
|
||||||
DateOfBirth = reader.GetString(7),
|
DateOfBirth = reader.GetString(dateOfBirthIndex),
|
||||||
Address = new UserAddress
|
Address = new UserAddress
|
||||||
{
|
{
|
||||||
City = cities[reader.GetInt64(8)],
|
City = cities[reader.GetInt64(cityIdIndex)],
|
||||||
PostalCode = reader.GetString(9),
|
PostalCode = reader.GetString(postalCodeIndex),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -102,7 +147,10 @@ public sealed class PipelineSeedDataReader
|
|||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IReadOnlyDictionary<long, City> ReadCities(SqliteConnection connection)
|
private static async Task<IReadOnlyDictionary<long, City>> ReadCitiesAsync(
|
||||||
|
SqliteConnection connection,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
{
|
{
|
||||||
const string sql = """
|
const string sql = """
|
||||||
SELECT
|
SELECT
|
||||||
@@ -118,33 +166,34 @@ public sealed class PipelineSeedDataReader
|
|||||||
FROM cities;
|
FROM cities;
|
||||||
""";
|
""";
|
||||||
|
|
||||||
using SqliteCommand command = new(sql, connection);
|
await using SqliteCommand command = new(sql, connection);
|
||||||
using SqliteDataReader reader = command.ExecuteReader();
|
await using SqliteDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||||
|
|
||||||
Dictionary<long, City> cities = [];
|
Dictionary<long, City> cities = [];
|
||||||
|
|
||||||
const int cityNameBindIndex = 1;
|
const int idIndex = 0;
|
||||||
const int stateProvinceBindIndex = 2;
|
const int cityNameIndex = 1;
|
||||||
const int iso31662BindIndex = 3;
|
const int stateProvinceIndex = 2;
|
||||||
const int countryBindIndex = 4;
|
const int iso31662Index = 3;
|
||||||
const int iso31661BindIndex = 5;
|
const int countryIndex = 4;
|
||||||
const int localLanguagesBindIndex = 6;
|
const int iso31661Index = 5;
|
||||||
const int countryFormatRegexBindIndex = 7;
|
const int localLanguagesIndex = 6;
|
||||||
const int cityRegexesBindIndex = 8;
|
const int countryFormatRegexIndex = 7;
|
||||||
|
const int cityRegexesIndex = 8;
|
||||||
|
|
||||||
while (reader.Read())
|
while (await reader.ReadAsync(cancellationToken))
|
||||||
cities[reader.GetInt64(0)] = new City
|
cities[reader.GetInt64(idIndex)] = new City
|
||||||
{
|
{
|
||||||
CityName = reader.GetString(cityNameBindIndex),
|
CityName = reader.GetString(cityNameIndex),
|
||||||
StateProvince = reader.GetString(stateProvinceBindIndex),
|
StateProvince = reader.GetString(stateProvinceIndex),
|
||||||
Iso31662 = reader.GetString(iso31662BindIndex),
|
Iso31662 = reader.GetString(iso31662Index),
|
||||||
Country = reader.GetString(countryBindIndex),
|
Country = reader.GetString(countryIndex),
|
||||||
Iso31661 = reader.GetString(iso31661BindIndex),
|
Iso31661 = reader.GetString(iso31661Index),
|
||||||
LocalLanguages = DeserializeStringArray(reader.GetString(localLanguagesBindIndex)),
|
LocalLanguages = DeserializeStringArray(reader.GetString(localLanguagesIndex)),
|
||||||
PostalCode = new PostalCodeSpec
|
PostalCode = new PostalCodeSpec
|
||||||
{
|
{
|
||||||
CountryFormatRegex = reader.GetString(countryFormatRegexBindIndex),
|
CountryFormatRegex = reader.GetString(countryFormatRegexIndex),
|
||||||
CityRegexes = DeserializeStringArray(reader.GetString(cityRegexesBindIndex)),
|
CityRegexes = DeserializeStringArray(reader.GetString(cityRegexesIndex)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user