1 using Microsoft.EntityFrameworkCore;
2 using Microsoft.EntityFrameworkCore.Infrastructure;
3 using Microsoft.EntityFrameworkCore.Migrations;
4 using Microsoft.Extensions.DependencyInjection;
5 using Microsoft.Extensions.Logging;
6 using Microsoft.Extensions.Options;
11 using System.Threading.Tasks;
20 public DbSet<User> Users {
get;
set; }
23 public DbSet<Instance> Instances {
get;
set; }
26 public DbSet<CompileJob> CompileJobs {
get;
set; }
29 public DbSet<RevisionInformation> RevisionInformations {
get;
set; }
35 public DbSet<ChatBot> ChatBots {
get;
set; }
44 public DbSet<InstanceUser> InstanceUsers {
get;
set; }
47 public DbSet<ChatChannel> ChatChannels {
get;
set; }
50 public DbSet<Job> Jobs {
get;
set; }
53 public DbSet<ReattachInformation> ReattachInformations {
get;
set; }
56 public DbSet<WatchdogReattachInformation> WatchdogReattachInformations {
get;
set; }
61 public DbSet<TestMerge> TestMerges {
get;
set; }
66 public DbSet<RevInfoTestMerge> RevInfoTestMerges {
get;
set; }
71 protected ILogger Logger {
get; }
90 public DatabaseContext(DbContextOptions<TParentContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfigurationOptions,
IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions)
92 DatabaseConfiguration = databaseConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(databaseConfigurationOptions));
93 this.databaseSeeder = databaseSeeder ??
throw new ArgumentNullException(nameof(databaseSeeder));
94 Logger = logger ??
throw new ArgumentNullException(nameof(logger));
100 Logger.LogTrace(
"Building entity framework context...");
101 base.OnModelCreating(modelBuilder);
103 var userModel = modelBuilder.Entity<
User>();
104 userModel.HasIndex(x => x.CanonicalName).IsUnique();
105 userModel.HasMany(x => x.TestMerges).WithOne(x => x.MergedBy).OnDelete(DeleteBehavior.Restrict);
107 modelBuilder.Entity<
InstanceUser>().HasIndex(x =>
new { x.UserId, x.InstanceId }).IsUnique();
109 modelBuilder.Entity<
TestMerge>().HasMany(x => x.RevisonInformations).WithOne(x => x.TestMerge).OnDelete(DeleteBehavior.Cascade);
112 revInfo.HasMany(x => x.CompileJobs).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade);
113 revInfo.HasMany(x => x.ActiveTestMerges).WithOne(x => x.RevisionInformation).OnDelete(DeleteBehavior.Cascade);
114 revInfo.HasOne(x => x.PrimaryTestMerge).WithOne(x => x.PrimaryRevisionInformation).OnDelete(DeleteBehavior.Restrict);
115 revInfo.HasIndex(x => x.CommitSha).IsUnique();
117 modelBuilder.Entity<
CompileJob>().HasIndex(x => x.DirectoryName);
119 modelBuilder.Entity<
Job>().HasOne<CompileJob>().WithOne(x => x.Job).OnDelete(DeleteBehavior.Restrict);
121 var chatChannel = modelBuilder.Entity<
ChatChannel>();
122 chatChannel.HasIndex(x =>
new { x.ChatSettingsId, x.IrcChannel }).IsUnique();
123 chatChannel.HasIndex(x =>
new { x.ChatSettingsId, x.DiscordChannelId }).IsUnique();
124 chatChannel.HasOne(x => x.ChatSettings).WithMany(x => x.Channels).HasForeignKey(x => x.ChatSettingsId).OnDelete(DeleteBehavior.Cascade);
126 modelBuilder.Entity<
ChatBot>().HasIndex(x => x.Name).IsUnique();
128 var instanceModel = modelBuilder.Entity<
Instance>();
129 instanceModel.HasIndex(x => x.Path).IsUnique();
130 instanceModel.HasMany(x => x.ChatSettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
131 instanceModel.HasOne(x => x.DreamDaemonSettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
132 instanceModel.HasOne(x => x.DreamMakerSettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
133 instanceModel.HasOne(x => x.RepositorySettings).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
134 instanceModel.HasMany(x => x.RevisionInformations).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
135 instanceModel.HasMany(x => x.InstanceUsers).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
136 instanceModel.HasMany(x => x.Jobs).WithOne(x => x.Instance).OnDelete(DeleteBehavior.Cascade);
137 instanceModel.HasOne(x => x.WatchdogReattachInformation).WithOne().OnDelete(DeleteBehavior.Cascade);
141 public async Task
Initialize(CancellationToken cancellationToken)
145 Logger.LogCritical(
"DropDatabase configuration option set! Dropping any existing database...");
146 await Database.EnsureDeletedAsync(cancellationToken).ConfigureAwait(
false);
149 var migrations = await Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(
false);
150 var wasEmpty = !migrations.Any();
152 if (wasEmpty || (await Database.GetPendingMigrationsAsync(cancellationToken).ConfigureAwait(
false)).Any())
154 Logger.LogInformation(
"Migrating database...");
155 await Database.MigrateAsync(cancellationToken).ConfigureAwait(
false);
158 Logger.LogDebug(
"No migrations to apply.");
160 wasEmpty |= (await Users.CountAsync(cancellationToken).ConfigureAwait(
false)) == 0;
164 Logger.LogInformation(
"Seeding database...");
165 await databaseSeeder.SeedDatabase(
this, cancellationToken).ConfigureAwait(
false);
169 Logger.LogWarning(
"Enabling and resetting admin password due to configuration!");
170 await databaseSeeder.ResetAdminPassword(
this, cancellationToken).ConfigureAwait(
false);
175 public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken);
181 protected abstract bool UseMySQLMigrations();
187 throw new ArgumentNullException(nameof(version));
188 if (version <
new Version(4, 0))
189 throw new ArgumentOutOfRangeException(nameof(version), version,
"Not a valid V4 version!");
191 string targetMigration = null;
202 if (targetMigration == null)
205 if (UseMySQLMigrations())
206 targetMigration = String.Format(CultureInfo.InvariantCulture,
"MY" + targetMigration.Substring(2));
209 var dbServiceProvider = ((IInfrastructure<IServiceProvider>)Database).Instance;
210 var migrator = dbServiceProvider.GetRequiredService<IMigrator>();
212 Logger.LogInformation(
"Migrating down to version {0}. Target: {1}", version, targetMigration);
215 await migrator.MigrateAsync(targetMigration, cancellationToken).ConfigureAwait(
false);
219 Logger.LogCritical(
"Failed to migrate! Exception: {0}", e);
Use server authentication
For initially seeding a database
async Task Initialize(CancellationToken cancellationToken)
Creates and migrates the IDatabaseContext
bool ResetAdminPassword
If the admin user should be enabled and have it's password reset
override void OnModelCreating(ModelBuilder modelBuilder)
async Task SchemaDowngradeForServerVersion(Version version, CancellationToken cancellationToken)
Attempt to downgrade the schema to the migration used for a given server version ...
Represents an Api.Models.Instance in the database
DatabaseContext(DbContextOptions< TParentContext > dbContextOptions, IOptions< DatabaseConfiguration > databaseConfigurationOptions, IDatabaseSeeder databaseSeeder, ILogger logger)
Construct a DatabaseContext<TParentContext>
Configuration options for the Models.DatabaseContext<TParentContext>
bool DropDatabase
If the database should be deleted on application startup. Should not be used in production! ...
readonly IDatabaseSeeder databaseSeeder
The IDatabaseSeeder for the DatabaseContext<TParentContext>