Browse Source
Adds a database seeder which can be used standalone using a CLI for seeding your local development environment, or used in unit tests to seed complex scenarios. --------- Co-authored-by: Robert Y <rkac@bitwarden.com>pull/5793/head
13 changed files with 367 additions and 0 deletions
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
using System.Net; |
||||
using System.Net.Http.Headers; |
||||
using Bit.Api.IntegrationTest.Factories; |
||||
using Bit.Seeder.Recipes; |
||||
using Xunit; |
||||
using Xunit.Abstractions; |
||||
|
||||
namespace Bit.Api.IntegrationTest.AdminConsole.Controllers; |
||||
|
||||
public class OrganizationUsersControllerPerformanceTest(ITestOutputHelper testOutputHelper) |
||||
{ |
||||
[Theory(Skip = "Performance test")] |
||||
[InlineData(100)] |
||||
[InlineData(60000)] |
||||
public async Task GetAsync(int seats) |
||||
{ |
||||
await using var factory = new ApiApplicationFactory(); |
||||
var client = factory.CreateClient(); |
||||
|
||||
var db = factory.GetDatabaseContext(); |
||||
var seeder = new OrganizationWithUsersRecipe(db); |
||||
|
||||
var orgId = seeder.Seed("Org", seats, "large.test"); |
||||
|
||||
var tokens = await factory.LoginAsync("admin@large.test", "c55hlJ/cfdvTd4awTXUqow6X3cOQCfGwn11o3HblnPs="); |
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token); |
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew(); |
||||
|
||||
var response = await client.GetAsync($"/organizations/{orgId}/users?includeCollections=true"); |
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
|
||||
var result = await response.Content.ReadAsStringAsync(); |
||||
Assert.NotEmpty(result); |
||||
|
||||
stopwatch.Stop(); |
||||
testOutputHelper.WriteLine($"Seed: {seats}; Request duration: {stopwatch.ElapsedMilliseconds} ms"); |
||||
} |
||||
} |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
||||
<PropertyGroup> |
||||
<OutputType>Exe</OutputType> |
||||
<TargetFramework>net8.0</TargetFramework> |
||||
<ImplicitUsings>enable</ImplicitUsings> |
||||
<Nullable>enable</Nullable> |
||||
<RootNamespace>Bit.DbSeederUtility</RootNamespace> |
||||
<AssemblyName>DbSeeder</AssemblyName> |
||||
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract> |
||||
<UserSecretsId>2294c6ba-7cd0-4293-a797-3882e41c61cb</UserSecretsId> |
||||
</PropertyGroup> |
||||
|
||||
<ItemGroup> |
||||
<ProjectReference Include="..\Seeder\Seeder.csproj" /> |
||||
</ItemGroup> |
||||
|
||||
<ItemGroup> |
||||
<PackageReference Include="CommandDotNet" Version="7.0.4" /> |
||||
</ItemGroup> |
||||
|
||||
</Project> |
||||
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
using Bit.Core.Settings; |
||||
using Microsoft.Extensions.Configuration; |
||||
|
||||
namespace Bit.DbSeederUtility; |
||||
|
||||
public static class GlobalSettingsFactory |
||||
{ |
||||
private static GlobalSettings? _globalSettings; |
||||
|
||||
public static GlobalSettings GlobalSettings |
||||
{ |
||||
get { return _globalSettings ??= LoadGlobalSettings(); } |
||||
} |
||||
|
||||
private static GlobalSettings LoadGlobalSettings() |
||||
{ |
||||
Console.WriteLine("Loading global settings..."); |
||||
|
||||
var configBuilder = new ConfigurationBuilder() |
||||
.SetBasePath(Directory.GetCurrentDirectory()) |
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) |
||||
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true, reloadOnChange: true) |
||||
.AddUserSecrets("bitwarden-Api") // Load user secrets from the API project |
||||
.AddEnvironmentVariables(); |
||||
|
||||
var configuration = configBuilder.Build(); |
||||
var globalSettingsSection = configuration.GetSection("globalSettings"); |
||||
|
||||
var settings = new GlobalSettings(); |
||||
globalSettingsSection.Bind(settings); |
||||
|
||||
return settings; |
||||
} |
||||
} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
using Bit.Infrastructure.EntityFramework.Repositories; |
||||
using Bit.Seeder.Recipes; |
||||
using CommandDotNet; |
||||
using Microsoft.Extensions.DependencyInjection; |
||||
|
||||
namespace Bit.DbSeederUtility; |
||||
|
||||
public class Program |
||||
{ |
||||
private static int Main(string[] args) |
||||
{ |
||||
return new AppRunner<Program>() |
||||
.Run(args); |
||||
} |
||||
|
||||
[Command("organization", Description = "Seed an organization and organization users")] |
||||
public void Organization( |
||||
[Option('n', "Name", Description = "Name of organization")] |
||||
string name, |
||||
[Option('u', "users", Description = "Number of users to generate")] |
||||
int users, |
||||
[Option('d', "domain", Description = "Email domain for users")] |
||||
string domain |
||||
) |
||||
{ |
||||
// Create service provider with necessary services |
||||
var services = new ServiceCollection(); |
||||
ServiceCollectionExtension.ConfigureServices(services); |
||||
var serviceProvider = services.BuildServiceProvider(); |
||||
|
||||
// Get a scoped DB context |
||||
using var scope = serviceProvider.CreateScope(); |
||||
var scopedServices = scope.ServiceProvider; |
||||
var db = scopedServices.GetRequiredService<DatabaseContext>(); |
||||
|
||||
var recipe = new OrganizationWithUsersRecipe(db); |
||||
recipe.Seed(name, users, domain); |
||||
} |
||||
} |
||||
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
# Bitwarden Database Seeder Utility |
||||
|
||||
A command-line utility for generating and managing test data for Bitwarden databases. |
||||
|
||||
## Overview |
||||
|
||||
DbSeederUtility is an executable wrapper around the Seeder class library that provides a convenient command-line |
||||
interface for executing seed-recipes in your local environment. |
||||
|
||||
## Installation |
||||
|
||||
The utility can be built and run as a .NET 8 application: |
||||
|
||||
``` |
||||
dotnet build |
||||
dotnet run -- <command> [options] |
||||
``` |
||||
|
||||
Or directly using the compiled executable: |
||||
|
||||
``` |
||||
DbSeeder.exe <command> [options] |
||||
``` |
||||
|
||||
## Examples |
||||
|
||||
### Generate and load test organization |
||||
|
||||
```bash |
||||
# Generate an organization called "seeded" with 10000 users using the @large.test email domain. |
||||
# Login using "admin@large.test" with password "asdfasdfasdf" |
||||
DbSeeder.exe organization -n seeded -u 10000 -d large.test |
||||
``` |
||||
|
||||
## Dependencies |
||||
|
||||
This utility depends on: |
||||
- The Seeder class library |
||||
- CommandDotNet for command-line parsing |
||||
- .NET 8.0 runtime |
||||
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
using Bit.SharedWeb.Utilities; |
||||
using Microsoft.AspNetCore.DataProtection; |
||||
using Microsoft.Extensions.DependencyInjection; |
||||
using Microsoft.Extensions.Logging; |
||||
|
||||
namespace Bit.DbSeederUtility; |
||||
|
||||
public static class ServiceCollectionExtension |
||||
{ |
||||
public static void ConfigureServices(ServiceCollection services) |
||||
{ |
||||
// Load configuration using the GlobalSettingsFactory |
||||
var globalSettings = GlobalSettingsFactory.GlobalSettings; |
||||
|
||||
// Register services |
||||
services.AddLogging(builder => builder.AddConsole()); |
||||
services.AddSingleton(globalSettings); |
||||
|
||||
// Add Data Protection services |
||||
services.AddDataProtection() |
||||
.SetApplicationName("Bitwarden"); |
||||
|
||||
services.AddDatabaseRepositories(globalSettings); |
||||
} |
||||
} |
||||
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
using Bit.Core.Billing.Enums; |
||||
using Bit.Core.Enums; |
||||
using Bit.Infrastructure.EntityFramework.AdminConsole.Models; |
||||
using Bit.Infrastructure.EntityFramework.Models; |
||||
|
||||
namespace Bit.Seeder.Factories; |
||||
|
||||
public class OrganizationSeeder |
||||
{ |
||||
public static Organization CreateEnterprise(string name, string domain, int seats) |
||||
{ |
||||
return new Organization |
||||
{ |
||||
Id = Guid.NewGuid(), |
||||
Name = name, |
||||
BillingEmail = $"billing@{domain}", |
||||
Plan = "Enterprise (Annually)", |
||||
PlanType = PlanType.EnterpriseAnnually, |
||||
Seats = seats, |
||||
|
||||
// Currently hardcoded to the values from https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-core/src/client/test_accounts.rs. |
||||
// TODO: These should be dynamically generated by the SDK. |
||||
PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmIJbGMk6eZqVE7UxhZ46Weu2jKciqOiOkSVYtGvs61rfe9AXxtLaaZEKN4d4DmkZcF6dna2eXNxZmb7U4pwlttye8ksqISe6IUAZQox7auBpjopdCEPhKRg3BD/u8ks9UxSxgWe+fpebjt6gd5hsl1/5HOObn7SeU6EEU04cp3/eH7a4OTdXxB8oN62HGV9kM/ubM1goILgjoSJDbihMK0eb7b8hPHwcA/YOgKKiu/N3FighccdSMD5Pk+HfjacsFNZQa2EsqW09IvvSZ+iL6HQeZ1vwc/6TO1J7EOfJZFQcjoEL9LVI693efYoMZSmrPEWziZ4PvwpOOGo6OObyMQIDAQAB", |
||||
PrivateKey = "2.6FggyKVyaKQsfohi5yqgbg==|UU2JeafOB41L5UscGmf4kq15JGDf3Bkf67KECiehTODzbWctVLTgyDk0Qco8/6CMN6nZGXjxR2A4r5ExhmwRNsNxd77G+MprkmiJz+7w33ROZ1ouQO5XjD3wbQ3ssqNiTKId6yAUPBvuAZRixVApauTuADc8QWGixqCQcqZzmU7YSBBIPf652/AEYr4Tk64YihoE39pHiK8MRbTLdRt3EF4LSMugPAPM24vCgUv3w1TD3Fj6sDg/6oi3flOV9SJZX4vCiUXbDNEuD/p2aQrEXVbaxweFOHjTe7F4iawjXw3nG3SO8rUBHcxbhDDVx5rjYactbW5QvHWiyla6uLb6o8WHBneg2EjTEwAHOZE/rBjcqmAJb2sVp1E0Kwq8ycGmL69vmqJPC1GqVTohAQvmEkaxIPpfq24Yb9ZPrADA7iEXBKuAQ1FphFUVgJBJGJbd60sOV1Rz1T+gUwS4wCNQ4l3LG1S22+wzUVlEku5DXFnT932tatqTyWEthqPqLCt6dL1+qa94XLpeHagXAx2VGe8n8IlcADtxqS+l8xQ4heT12WO9kC316vqvg1mnsI56faup9hb3eT9ZpKyxSBGYOphlTWfV1Y/v64f5PYvTo4aL0IYHyLY/9Qi72vFmOpPeHBYgD5t3j+H2CsiU1PkYsBggOmD7xW8FDuT6HWVvwhEJqeibVPK0Lhyj6tgvlSIAvFUaSMFPlmwFNmwfj/AHUhr9KuTfsBFTZ10yy9TZVgf+EofwnrxHBaWUgdD40aHoY1VjfG33iEuajb6buxG3pYFyPNhJNzeLZisUKIDRMQpUHrsE22EyrFFran3tZGdtcyIEK4Q1F0ULYzJ6T9iY25/ZgPy3pEAAMZCtqo3s+GjX295fWIHfMcnjMgNUHPjExjWBHa+ggK9iQXkFpBVyYB1ga/+0eiIhiek3PlgtvpDrqF7TsLK+ROiBw2GJ7uaO3EEXOj2GpNBuEJ5CdodhZkwzhwMcSatgDHkUuNVu0iVbF6/MxVdOxWXKO+jCYM6PZk/vAhLYqpPzu2T2Uyz4nkDs2Tiq61ez6FoCrzdHIiyIxVTzUQH8G9FgSmtaZ7GCbqlhnurYgcMciwPzxg0hpAQT+NZw1tVEii9vFSpJJbGJqNhORKfKh/Mu1P/9LOQq7Y0P2FIR3x/eUVEQ7CGv2jVtO5ryGSmKeq/P9Fr54wTPaNiqN2K+leACUznCdUWw8kZo/AsBcrOe4OkRX6k8LC3oeJXy06DEToatxEvPYemUauhxiXRw8nfNMqc4LyJq2bbT0zCgJHoqpozPdNg6AYWcoIobgAGu7ZQGq+oE1MT3GZxotMPe/NUJiAc5YE9Thb5Yf3gyno71pyqPTVl/6IQuh4SUz7rkgwF/aVHEnr4aUYNoc0PEzd2Me0jElsA3GAneq1I/wngutOWgTViTK4Nptr5uIzMVQs9H1rOMJNorP8b02t1NDu010rSsib9GaaJJq4r4iy46laQOxWoU0ex26arYnk+jw4833WSCTVBIprTgizZ+fKjoY0xwXvI2oOvGNEUCtGFvKFORTaQrlaXZIg1toa2BBVNicyONbwnI3KIu3MgGJ2SlCVXJn8oHFppVHFCdwgN1uDzGiKAhjvr0sZTUtXin2f2CszPTbbo=|fUhbVKrr8CSKE7TZJneXpDGraj5YhRrq9ESo206S+BY=", |
||||
}; |
||||
} |
||||
} |
||||
|
||||
public static class OrgnaizationExtensions |
||||
{ |
||||
public static OrganizationUser CreateOrganizationUser(this Organization organization, User user) |
||||
{ |
||||
return new OrganizationUser |
||||
{ |
||||
Id = Guid.NewGuid(), |
||||
OrganizationId = organization.Id, |
||||
UserId = user.Id, |
||||
|
||||
Key = "4.rY01mZFXHOsBAg5Fq4gyXuklWfm6mQASm42DJpx05a+e2mmp+P5W6r54WU2hlREX0uoTxyP91bKKwickSPdCQQ58J45LXHdr9t2uzOYyjVzpzebFcdMw1eElR9W2DW8wEk9+mvtWvKwu7yTebzND+46y1nRMoFydi5zPVLSlJEf81qZZ4Uh1UUMLwXz+NRWfixnGXgq2wRq1bH0n3mqDhayiG4LJKgGdDjWXC8W8MMXDYx24SIJrJu9KiNEMprJE+XVF9nQVNijNAjlWBqkDpsfaWTUfeVLRLctfAqW1blsmIv4RQ91PupYJZDNc8nO9ZTF3TEVM+2KHoxzDJrLs2Q==", |
||||
Type = OrganizationUserType.Admin, |
||||
Status = OrganizationUserStatusType.Confirmed |
||||
}; |
||||
} |
||||
} |
||||
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
using Bit.Core.Enums; |
||||
using Bit.Infrastructure.EntityFramework.Models; |
||||
|
||||
namespace Bit.Seeder.Factories; |
||||
|
||||
public class UserSeeder |
||||
{ |
||||
public static User CreateUser(string email) |
||||
{ |
||||
return new User |
||||
{ |
||||
Id = Guid.NewGuid(), |
||||
Email = email, |
||||
MasterPassword = "AQAAAAIAAYagAAAAEBATmF66OHMpHuHKc1CsGZQ1ltHUHyhYK+7e4re3bVFi16SOpLpDfzdFswnvFQs2Rg==", |
||||
SecurityStamp = "4830e359-e150-4eae-be2a-996c81c5e609", |
||||
Key = "2.z/eLKFhd62qy9RzXu3UHgA==|fF6yNupiCIguFKSDTB3DoqcGR0Xu4j+9VlnMyT5F3PaWIcGhzQKIzxdB95nhslaCQv3c63M7LBnvzVo1J9SUN85RMbP/57bP1HvhhU1nvL8=|IQPtf8v7k83MFZEhazSYXSdu98BBU5rqtvC4keVWyHM=", |
||||
PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Ww2chogqCpaAR7Uw448am4b7vDFXiM5kXjFlGfXBlrAdAqTTggEvTDlMNYqPlCo+mBM6iFmTTUY9rpZBvFskMnKvsvpJ47/fehAH2o2e3Ulv/5NFevaVCMCmpkBDtbMbO1A4a3btdRtCP8DsKWMefHauEpaoLxNTLWnOIZVfCMjsSgx2EvULHAZPTtbFwm4+UVKniM4ds4jvOsD85h4jn2aLs/jWJXFfxN8iVSqEqpC2TBvsPdyHb49xQoWWfF0Z6BiNqeNGKEU9Uos1pjL+kzhEzzSpH31PZT/ufJ/oo4+93wrUt57hb6f0jxiXhwd5yQ+9F6wVwpbfkq0IwhjOwIDAQAB", |
||||
PrivateKey = "2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=", |
||||
ApiKey = "7gp59kKHt9kMlks0BuNC4IjNXYkljR", |
||||
|
||||
Kdf = KdfType.PBKDF2_SHA256, |
||||
KdfIterations = 600_000, |
||||
}; |
||||
} |
||||
} |
||||
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
# Bitwarden Database Seeder |
||||
|
||||
A class library for generating and inserting test data. |
||||
|
||||
## Project Structure |
||||
|
||||
The project is organized into these main components: |
||||
|
||||
### Factories |
||||
|
||||
Factories are helper classes for creating domain entities and populating them with realistic data. This assist in |
||||
decreasing the amount of boilerplate code needed to create test data in recipes. |
||||
|
||||
### Recipes |
||||
|
||||
Recipes are pre-defined data sets which can be run to generate and load data into the database. They often allow a allow |
||||
for a few arguments to customize the data slightly. Recipes should be kept simple and focused on a single task. Default |
||||
to creating more recipes rather than adding complexity to existing ones. |
||||
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
using Bit.Infrastructure.EntityFramework.Models; |
||||
using Bit.Infrastructure.EntityFramework.Repositories; |
||||
using Bit.Seeder.Factories; |
||||
using LinqToDB.EntityFrameworkCore; |
||||
|
||||
namespace Bit.Seeder.Recipes; |
||||
|
||||
public class OrganizationWithUsersRecipe(DatabaseContext db) |
||||
{ |
||||
public Guid Seed(string name, int users, string domain) |
||||
{ |
||||
var organization = OrganizationSeeder.CreateEnterprise(name, domain, users); |
||||
var user = UserSeeder.CreateUser($"admin@{domain}"); |
||||
var orgUser = organization.CreateOrganizationUser(user); |
||||
|
||||
var additionalUsers = new List<User>(); |
||||
var additionalOrgUsers = new List<OrganizationUser>(); |
||||
for (var i = 0; i < users; i++) |
||||
{ |
||||
var additionalUser = UserSeeder.CreateUser($"user{i}@{domain}"); |
||||
additionalUsers.Add(additionalUser); |
||||
additionalOrgUsers.Add(organization.CreateOrganizationUser(additionalUser)); |
||||
} |
||||
|
||||
db.Add(organization); |
||||
db.Add(user); |
||||
db.Add(orgUser); |
||||
|
||||
db.SaveChanges(); |
||||
|
||||
// Use LinqToDB's BulkCopy for significant better performance |
||||
db.BulkCopy(additionalUsers); |
||||
db.BulkCopy(additionalOrgUsers); |
||||
|
||||
return organization.Id; |
||||
} |
||||
} |
||||
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
||||
<PropertyGroup> |
||||
<TargetFramework>net8.0</TargetFramework> |
||||
<ImplicitUsings>enable</ImplicitUsings> |
||||
<Nullable>enable</Nullable> |
||||
<RootNamespace>Bit.Seeder</RootNamespace> |
||||
<UserSecretsId>Bit.Seeder</UserSecretsId> |
||||
<Description>Core library for generating and managing test data for Bitwarden</Description> |
||||
<OutputType>library</OutputType> |
||||
<IsPackable>false</IsPackable> |
||||
</PropertyGroup> |
||||
|
||||
<ItemGroup> |
||||
<Folder Include="Settings\" /> |
||||
</ItemGroup> |
||||
|
||||
<ItemGroup> |
||||
<ProjectReference Include="..\..\src\Core\Core.csproj" /> |
||||
<ProjectReference Include="..\..\src\Infrastructure.EntityFramework\Infrastructure.EntityFramework.csproj" /> |
||||
<ProjectReference Include="..\..\src\SharedWeb\SharedWeb.csproj" /> |
||||
</ItemGroup> |
||||
|
||||
<ItemGroup> |
||||
<Compile Remove="..\..\Program.cs" /> |
||||
</ItemGroup> |
||||
|
||||
</Project> |
||||
Loading…
Reference in new issue