-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial commit for EF db seeder #53
base: main
Are you sure you want to change the base?
Changes from all commits
a4aa40c
d21998c
f87462e
e9b4d09
6b03d9f
82201c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
๏ปฟ<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Bogus" Version="35.5.1" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\src\Core\Core.csproj" /> | ||
<ProjectReference Include="..\..\src\Api\Api.csproj" /> | ||
<ProjectReference Include="..\..\src\Infrastructure.EntityFramework\Infrastructure.EntityFramework.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
๏ปฟusing System.Text.Json; | ||
using Bit.Core.Vault.Enums; // Change this line | ||
using Bit.Infrastructure.EntityFramework.Repositories; | ||
using Bit.Infrastructure.EntityFramework.Vault.Models; | ||
using Bogus; | ||
using Microsoft.Extensions.Logging; | ||
|
||
|
||
|
||
namespace Bit.DBSeeder; | ||
|
||
public class EFDBSeeder | ||
{ | ||
private readonly string _connectionString; | ||
private readonly string _databaseProvider; | ||
private readonly ILogger<EFDBSeeder> _logger; | ||
|
||
public EFDBSeeder(string connectionString, string databaseProvider) | ||
{ | ||
_connectionString = connectionString; | ||
_databaseProvider = databaseProvider; | ||
|
||
} | ||
|
||
public bool SeedDatabase() | ||
{ | ||
//print connectionstring to console | ||
Console.WriteLine(_connectionString); | ||
Console.WriteLine(_databaseProvider); | ||
|
||
try | ||
{ | ||
var factory = new DatabaseContextFactory(); | ||
using (var context = factory.CreateDbContext(new[] { _connectionString })) | ||
Comment on lines
+33
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: DatabaseContextFactory is not using the _databaseProvider. Ensure it's correctly configured for different database types |
||
{ | ||
if (context.Database.CanConnect()) | ||
{ | ||
Console.WriteLine("Successfully connected to the database!"); | ||
|
||
// Seed the database | ||
SeedUsers(context); | ||
SeedCiphers(context); | ||
|
||
Console.WriteLine("Database seeded successfully!"); | ||
} | ||
else | ||
{ | ||
Console.WriteLine("Failed to connect to the database."); | ||
return false; | ||
} | ||
} | ||
} | ||
catch (Exception ex) | ||
{ | ||
Console.WriteLine($"Error adding users: {ex.Message}"); | ||
if (ex.InnerException != null) | ||
{ | ||
Console.WriteLine($"Inner exception: {ex.InnerException.Message}"); | ||
} | ||
throw; // Re-throw the exception to stop the seeding process | ||
} | ||
|
||
|
||
|
||
return true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: This always returns true, even if an exception is thrown. Consider returning false on exceptions |
||
} | ||
|
||
private void SeedUsers(DatabaseContext context) | ||
{ | ||
if (!context.Users.Any()) | ||
{ | ||
Console.WriteLine("Generating 5000 users..."); | ||
|
||
var faker = new Faker<Bit.Infrastructure.EntityFramework.Models.User>() | ||
.RuleFor(u => u.Id, f => Guid.NewGuid()) | ||
.RuleFor(u => u.Name, f => f.Name.FullName()) | ||
.RuleFor(u => u.Email, (f, u) => f.Internet.Email(u.Name)) | ||
.RuleFor(u => u.EmailVerified, f => f.Random.Bool(0.9f)) | ||
.RuleFor(u => u.SecurityStamp, f => Guid.NewGuid().ToString()) | ||
.RuleFor(u => u.ApiKey, f => Guid.NewGuid().ToString("N").Substring(0, 30)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: ApiKey generation might not be secure. Consider using a cryptographically secure method |
||
.RuleFor(u => u.CreationDate, f => f.Date.Past(2)) | ||
.RuleFor(u => u.RevisionDate, (f, u) => f.Date.Between(u.CreationDate, DateTime.UtcNow)); | ||
|
||
var users = faker.Generate(5000); | ||
|
||
const int batchSize = 100; | ||
for (int i = 0; i < users.Count; i += batchSize) | ||
{ | ||
context.Users.AddRange(users.Skip(i).Take(batchSize)); | ||
context.SaveChanges(); | ||
Console.WriteLine($"Added {Math.Min(i + batchSize, users.Count)} users"); | ||
} | ||
|
||
Console.WriteLine("5000 test users added to the database."); | ||
} | ||
else | ||
{ | ||
Console.WriteLine("Users table is not empty. Skipping user seeding."); | ||
} | ||
} | ||
|
||
private void SeedCiphers(DatabaseContext context) | ||
{ | ||
if (!context.Ciphers.Any()) | ||
{ | ||
var users = context.Users.ToList(); | ||
if (!users.Any()) | ||
{ | ||
Console.WriteLine("No users found. Please seed users first."); | ||
return; | ||
} | ||
|
||
Console.WriteLine($"Generating ciphers for {users.Count} users..."); | ||
|
||
var faker = new Faker<Cipher>() | ||
.RuleFor(c => c.Id, f => Guid.NewGuid()) | ||
.RuleFor(c => c.Type, f => CipherType.Login) | ||
.RuleFor(c => c.Data, f => JsonSerializer.Serialize(new | ||
{ | ||
Name = f.Internet.DomainName(), | ||
Notes = f.Lorem.Sentence(), | ||
Login = new | ||
{ | ||
Username = f.Internet.UserName(), | ||
Password = f.Internet.Password(), | ||
Uri = f.Internet.Url() | ||
} | ||
})) | ||
.RuleFor(c => c.CreationDate, f => f.Date.Past(1)) | ||
.RuleFor(c => c.RevisionDate, (f, c) => f.Date.Between(c.CreationDate, DateTime.UtcNow)) | ||
.RuleFor(c => c.DeletedDate, f => null) | ||
.RuleFor(c => c.Reprompt, f => CipherRepromptType.None); | ||
|
||
const int batchSize = 100; | ||
for (int i = 0; i < users.Count; i += batchSize) | ||
{ | ||
var userBatch = users.Skip(i).Take(batchSize); | ||
var ciphers = userBatch.Select(user => | ||
{ | ||
var cipher = faker.Generate(); | ||
cipher.UserId = user.Id; | ||
return cipher; | ||
}).ToList(); | ||
|
||
try | ||
{ | ||
context.Ciphers.AddRange(ciphers); | ||
context.SaveChanges(); | ||
Console.WriteLine($"Added ciphers for users {i + 1} to {Math.Min(i + batchSize, users.Count)}"); | ||
} | ||
catch (Exception ex) | ||
{ | ||
Console.WriteLine($"Error adding ciphers: {ex.Message}"); | ||
if (ex.InnerException != null) | ||
{ | ||
Console.WriteLine($"Inner exception: {ex.InnerException.Message}"); | ||
} | ||
throw; // Re-throw the exception to stop the seeding process | ||
} | ||
} | ||
|
||
Console.WriteLine($"Ciphers added for all {users.Count} users."); | ||
} | ||
else | ||
{ | ||
Console.WriteLine("Ciphers table is not empty. Skipping cipher seeding."); | ||
} | ||
} | ||
|
||
/* private ILogger<EFDBSeeder> CreateLogger() | ||
{ | ||
var loggerFactory = LoggerFactory.Create(builder => | ||
{ | ||
builder | ||
.AddFilter("Microsoft", LogLevel.Warning) | ||
.AddFilter("System", LogLevel.Warning) | ||
.AddConsole(); | ||
|
||
builder.AddFilter("EFDBSeeder.EFDBSeeder", LogLevel.Information); | ||
}); | ||
|
||
return loggerFactory.CreateLogger<EFDBSeeder>(); | ||
} | ||
*/ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
๏ปฟusing Bit.Infrastructure.EntityFramework.Repositories; | ||
using Microsoft.EntityFrameworkCore; | ||
using Microsoft.EntityFrameworkCore.Design; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace Bit.DBSeeder; | ||
|
||
|
||
public class DatabaseContextFactory : IDesignTimeDbContextFactory<DatabaseContext> | ||
{ | ||
public DatabaseContext CreateDbContext(string[] args) | ||
{ | ||
if (args.Length == 0 || string.IsNullOrWhiteSpace(args[0])) | ||
{ | ||
throw new ArgumentException("Connection string must be provided as the first argument."); | ||
} | ||
|
||
var connectionString = args[0]; | ||
|
||
var services = new ServiceCollection(); | ||
services.AddDataProtection(); | ||
services.AddDbContext<DatabaseContext>(options => | ||
options.UseSqlServer(connectionString)); | ||
Comment on lines
+22
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: This hardcodes SQL Server. Consider using a provider-agnostic approach to support multiple database types. |
||
var serviceProvider = services.BuildServiceProvider(); | ||
Comment on lines
+20
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: ServiceCollection is created and immediately disposed. Consider passing IServiceProvider as a parameter for better testability and reusability. |
||
|
||
return serviceProvider.GetRequiredService<DatabaseContext>(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
๏ปฟ<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\DBSeeder\DBSeeder.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="CommandDotNet" Version="7.0.4" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" /> | ||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
๏ปฟusing Bit.DBSeeder; | ||
using CommandDotNet; | ||
|
||
internal class Program | ||
{ | ||
private static int Main(string[] args) | ||
{ | ||
return new AppRunner<Program>().Run(args); | ||
} | ||
|
||
[DefaultCommand] | ||
public void Execute( | ||
|
||
[Operand(Description = "Database provider (mssql, mysql, postgres, sqlite).")] string databaseProvider, | ||
[Operand(Description = "Database connection string.")] string ConnectionString | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: ConnectionString parameter name should be camelCase for consistency |
||
|
||
) => SeedDatabase(ConnectionString, databaseProvider); | ||
|
||
private static bool SeedDatabase(string databaseConnectionString, | ||
string databaseProvider) | ||
{ | ||
var seeder = new EFDBSeeder(databaseConnectionString, databaseProvider); | ||
bool success; | ||
|
||
success = seeder.SeedDatabase(); // Change this line | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Remove the comment '// Change this line' as it's no longer relevant |
||
|
||
return success; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: ILogger is declared but never used