Phase 1 WP-1: EF Core DbContext with Fluent API mappings for all 26 entities

ScadaLinkDbContext with 10 configuration classes (Fluent API only), initial
migration creating 25 tables, environment-aware migration helper (auto-apply
dev, validate-only prod), DesignTimeDbContextFactory, optimistic concurrency
on DeploymentRecord. 20 tests verify schema, CRUD, relationships, cascades.
This commit is contained in:
Joseph Doherty
2026-03-16 19:15:50 -04:00
parent 9bc5a5163f
commit 1996b21961
23 changed files with 4494 additions and 9 deletions

View File

@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ScadaLink.Commons.Entities.Audit;
namespace ScadaLink.ConfigurationDatabase.Configurations;
public class AuditLogEntryConfiguration : IEntityTypeConfiguration<AuditLogEntry>
{
public void Configure(EntityTypeBuilder<AuditLogEntry> builder)
{
builder.HasKey(a => a.Id);
builder.Property(a => a.User)
.IsRequired()
.HasMaxLength(200);
builder.Property(a => a.Action)
.IsRequired()
.HasMaxLength(100);
builder.Property(a => a.EntityType)
.IsRequired()
.HasMaxLength(200);
builder.Property(a => a.EntityId)
.IsRequired()
.HasMaxLength(200);
builder.Property(a => a.EntityName)
.IsRequired()
.HasMaxLength(200);
// Indexes for common query patterns
builder.HasIndex(a => a.Timestamp);
builder.HasIndex(a => a.User);
builder.HasIndex(a => a.EntityType);
builder.HasIndex(a => a.EntityId);
builder.HasIndex(a => a.Action);
}
}

View File

@@ -0,0 +1,63 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ScadaLink.Commons.Entities.Deployment;
using ScadaLink.Commons.Entities.Instances;
namespace ScadaLink.ConfigurationDatabase.Configurations;
public class DeploymentRecordConfiguration : IEntityTypeConfiguration<DeploymentRecord>
{
public void Configure(EntityTypeBuilder<DeploymentRecord> builder)
{
builder.HasKey(d => d.Id);
builder.Property(d => d.DeploymentId)
.IsRequired()
.HasMaxLength(100);
builder.Property(d => d.RevisionHash)
.HasMaxLength(100);
builder.Property(d => d.DeployedBy)
.IsRequired()
.HasMaxLength(200);
builder.Property(d => d.Status)
.HasConversion<string>()
.HasMaxLength(50);
builder.HasOne<Instance>()
.WithMany()
.HasForeignKey(d => d.InstanceId)
.OnDelete(DeleteBehavior.Restrict);
// Optimistic concurrency on deployment status records
builder.Property<byte[]>("RowVersion")
.IsRowVersion();
builder.HasIndex(d => d.DeploymentId).IsUnique();
builder.HasIndex(d => d.InstanceId);
builder.HasIndex(d => d.DeployedAt);
}
}
public class SystemArtifactDeploymentRecordConfiguration : IEntityTypeConfiguration<SystemArtifactDeploymentRecord>
{
public void Configure(EntityTypeBuilder<SystemArtifactDeploymentRecord> builder)
{
builder.HasKey(d => d.Id);
builder.Property(d => d.ArtifactType)
.IsRequired()
.HasMaxLength(100);
builder.Property(d => d.DeployedBy)
.IsRequired()
.HasMaxLength(200);
builder.Property(d => d.PerSiteStatus)
.HasMaxLength(4000);
builder.HasIndex(d => d.DeployedAt);
}
}

View File

@@ -0,0 +1,81 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ScadaLink.Commons.Entities.ExternalSystems;
namespace ScadaLink.ConfigurationDatabase.Configurations;
public class ExternalSystemDefinitionConfiguration : IEntityTypeConfiguration<ExternalSystemDefinition>
{
public void Configure(EntityTypeBuilder<ExternalSystemDefinition> builder)
{
builder.HasKey(e => e.Id);
builder.Property(e => e.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(e => e.EndpointUrl)
.IsRequired()
.HasMaxLength(2000);
builder.Property(e => e.AuthType)
.IsRequired()
.HasMaxLength(50);
builder.Property(e => e.AuthConfiguration)
.HasMaxLength(4000);
builder.HasMany<ExternalSystemMethod>()
.WithOne()
.HasForeignKey(m => m.ExternalSystemDefinitionId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasIndex(e => e.Name).IsUnique();
}
}
public class ExternalSystemMethodConfiguration : IEntityTypeConfiguration<ExternalSystemMethod>
{
public void Configure(EntityTypeBuilder<ExternalSystemMethod> builder)
{
builder.HasKey(m => m.Id);
builder.Property(m => m.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(m => m.HttpMethod)
.IsRequired()
.HasMaxLength(10);
builder.Property(m => m.Path)
.IsRequired()
.HasMaxLength(2000);
builder.Property(m => m.ParameterDefinitions)
.HasMaxLength(4000);
builder.Property(m => m.ReturnDefinition)
.HasMaxLength(4000);
builder.HasIndex(m => new { m.ExternalSystemDefinitionId, m.Name }).IsUnique();
}
}
public class DatabaseConnectionDefinitionConfiguration : IEntityTypeConfiguration<DatabaseConnectionDefinition>
{
public void Configure(EntityTypeBuilder<DatabaseConnectionDefinition> builder)
{
builder.HasKey(d => d.Id);
builder.Property(d => d.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(d => d.ConnectionString)
.IsRequired()
.HasMaxLength(4000);
builder.HasIndex(d => d.Name).IsUnique();
}
}

View File

@@ -0,0 +1,50 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ScadaLink.Commons.Entities.InboundApi;
namespace ScadaLink.ConfigurationDatabase.Configurations;
public class ApiKeyConfiguration : IEntityTypeConfiguration<ApiKey>
{
public void Configure(EntityTypeBuilder<ApiKey> builder)
{
builder.HasKey(k => k.Id);
builder.Property(k => k.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(k => k.KeyValue)
.IsRequired()
.HasMaxLength(500);
builder.HasIndex(k => k.Name).IsUnique();
builder.HasIndex(k => k.KeyValue).IsUnique();
}
}
public class ApiMethodConfiguration : IEntityTypeConfiguration<ApiMethod>
{
public void Configure(EntityTypeBuilder<ApiMethod> builder)
{
builder.HasKey(m => m.Id);
builder.Property(m => m.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(m => m.Script)
.IsRequired();
builder.Property(m => m.ApprovedApiKeyIds)
.HasMaxLength(4000);
builder.Property(m => m.ParameterDefinitions)
.HasMaxLength(4000);
builder.Property(m => m.ReturnDefinition)
.HasMaxLength(4000);
builder.HasIndex(m => m.Name).IsUnique();
}
}

View File

@@ -0,0 +1,113 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ScadaLink.Commons.Entities.Instances;
using ScadaLink.Commons.Entities.Sites;
using ScadaLink.Commons.Entities.Templates;
namespace ScadaLink.ConfigurationDatabase.Configurations;
public class InstanceConfiguration : IEntityTypeConfiguration<Instance>
{
public void Configure(EntityTypeBuilder<Instance> builder)
{
builder.HasKey(i => i.Id);
builder.Property(i => i.UniqueName)
.IsRequired()
.HasMaxLength(200);
builder.Property(i => i.State)
.HasConversion<string>()
.HasMaxLength(50);
builder.HasOne<Template>()
.WithMany()
.HasForeignKey(i => i.TemplateId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<Site>()
.WithMany()
.HasForeignKey(i => i.SiteId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<Area>()
.WithMany()
.HasForeignKey(i => i.AreaId)
.OnDelete(DeleteBehavior.SetNull)
.IsRequired(false);
builder.HasMany(i => i.AttributeOverrides)
.WithOne()
.HasForeignKey(o => o.InstanceId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(i => i.ConnectionBindings)
.WithOne()
.HasForeignKey(b => b.InstanceId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasIndex(i => new { i.SiteId, i.UniqueName }).IsUnique();
}
}
public class InstanceAttributeOverrideConfiguration : IEntityTypeConfiguration<InstanceAttributeOverride>
{
public void Configure(EntityTypeBuilder<InstanceAttributeOverride> builder)
{
builder.HasKey(o => o.Id);
builder.Property(o => o.AttributeName)
.IsRequired()
.HasMaxLength(200);
builder.Property(o => o.OverrideValue)
.HasMaxLength(4000);
builder.HasIndex(o => new { o.InstanceId, o.AttributeName }).IsUnique();
}
}
public class InstanceConnectionBindingConfiguration : IEntityTypeConfiguration<InstanceConnectionBinding>
{
public void Configure(EntityTypeBuilder<InstanceConnectionBinding> builder)
{
builder.HasKey(b => b.Id);
builder.Property(b => b.AttributeName)
.IsRequired()
.HasMaxLength(200);
builder.HasOne<DataConnection>()
.WithMany()
.HasForeignKey(b => b.DataConnectionId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(b => new { b.InstanceId, b.AttributeName }).IsUnique();
}
}
public class AreaConfiguration : IEntityTypeConfiguration<Area>
{
public void Configure(EntityTypeBuilder<Area> builder)
{
builder.HasKey(a => a.Id);
builder.Property(a => a.Name)
.IsRequired()
.HasMaxLength(200);
builder.HasOne<Site>()
.WithMany()
.HasForeignKey(a => a.SiteId)
.OnDelete(DeleteBehavior.Cascade);
// Self-referencing parent area
builder.HasOne<Area>()
.WithMany(a => a.Children)
.HasForeignKey(a => a.ParentAreaId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
builder.HasIndex(a => new { a.SiteId, a.ParentAreaId, a.Name }).IsUnique();
}
}

View File

@@ -0,0 +1,66 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ScadaLink.Commons.Entities.Notifications;
namespace ScadaLink.ConfigurationDatabase.Configurations;
public class NotificationListConfiguration : IEntityTypeConfiguration<NotificationList>
{
public void Configure(EntityTypeBuilder<NotificationList> builder)
{
builder.HasKey(n => n.Id);
builder.Property(n => n.Name)
.IsRequired()
.HasMaxLength(200);
builder.HasMany(n => n.Recipients)
.WithOne()
.HasForeignKey(r => r.NotificationListId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasIndex(n => n.Name).IsUnique();
}
}
public class NotificationRecipientConfiguration : IEntityTypeConfiguration<NotificationRecipient>
{
public void Configure(EntityTypeBuilder<NotificationRecipient> builder)
{
builder.HasKey(r => r.Id);
builder.Property(r => r.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(r => r.EmailAddress)
.IsRequired()
.HasMaxLength(500);
}
}
public class SmtpConfigurationConfiguration : IEntityTypeConfiguration<SmtpConfiguration>
{
public void Configure(EntityTypeBuilder<SmtpConfiguration> builder)
{
builder.HasKey(s => s.Id);
builder.Property(s => s.Host)
.IsRequired()
.HasMaxLength(500);
builder.Property(s => s.AuthType)
.IsRequired()
.HasMaxLength(50);
builder.Property(s => s.Credentials)
.HasMaxLength(4000);
builder.Property(s => s.TlsMode)
.HasMaxLength(50);
builder.Property(s => s.FromAddress)
.IsRequired()
.HasMaxLength(500);
}
}

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ScadaLink.Commons.Entities.Scripts;
namespace ScadaLink.ConfigurationDatabase.Configurations;
public class SharedScriptConfiguration : IEntityTypeConfiguration<SharedScript>
{
public void Configure(EntityTypeBuilder<SharedScript> builder)
{
builder.HasKey(s => s.Id);
builder.Property(s => s.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(s => s.Code)
.IsRequired();
builder.Property(s => s.ParameterDefinitions)
.HasMaxLength(4000);
builder.Property(s => s.ReturnDefinition)
.HasMaxLength(4000);
builder.HasIndex(s => s.Name).IsUnique();
}
}

View File

@@ -0,0 +1,44 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ScadaLink.Commons.Entities.Security;
using ScadaLink.Commons.Entities.Sites;
namespace ScadaLink.ConfigurationDatabase.Configurations;
public class LdapGroupMappingConfiguration : IEntityTypeConfiguration<LdapGroupMapping>
{
public void Configure(EntityTypeBuilder<LdapGroupMapping> builder)
{
builder.HasKey(m => m.Id);
builder.Property(m => m.LdapGroupName)
.IsRequired()
.HasMaxLength(500);
builder.Property(m => m.Role)
.IsRequired()
.HasMaxLength(100);
builder.HasIndex(m => m.LdapGroupName).IsUnique();
}
}
public class SiteScopeRuleConfiguration : IEntityTypeConfiguration<SiteScopeRule>
{
public void Configure(EntityTypeBuilder<SiteScopeRule> builder)
{
builder.HasKey(r => r.Id);
builder.HasOne<LdapGroupMapping>()
.WithMany()
.HasForeignKey(r => r.LdapGroupMappingId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<Site>()
.WithMany()
.HasForeignKey(r => r.SiteId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasIndex(r => new { r.LdapGroupMappingId, r.SiteId }).IsUnique();
}
}

View File

@@ -0,0 +1,68 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ScadaLink.Commons.Entities.Sites;
namespace ScadaLink.ConfigurationDatabase.Configurations;
public class SiteConfiguration : IEntityTypeConfiguration<Site>
{
public void Configure(EntityTypeBuilder<Site> builder)
{
builder.HasKey(s => s.Id);
builder.Property(s => s.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(s => s.SiteIdentifier)
.IsRequired()
.HasMaxLength(100);
builder.Property(s => s.Description)
.HasMaxLength(2000);
builder.HasIndex(s => s.Name).IsUnique();
builder.HasIndex(s => s.SiteIdentifier).IsUnique();
}
}
public class DataConnectionConfiguration : IEntityTypeConfiguration<DataConnection>
{
public void Configure(EntityTypeBuilder<DataConnection> builder)
{
builder.HasKey(d => d.Id);
builder.Property(d => d.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(d => d.Protocol)
.IsRequired()
.HasMaxLength(50);
builder.Property(d => d.Configuration)
.HasMaxLength(4000);
builder.HasIndex(d => d.Name).IsUnique();
}
}
public class SiteDataConnectionAssignmentConfiguration : IEntityTypeConfiguration<SiteDataConnectionAssignment>
{
public void Configure(EntityTypeBuilder<SiteDataConnectionAssignment> builder)
{
builder.HasKey(a => a.Id);
builder.HasOne<Site>()
.WithMany()
.HasForeignKey(a => a.SiteId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<DataConnection>()
.WithMany()
.HasForeignKey(a => a.DataConnectionId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasIndex(a => new { a.SiteId, a.DataConnectionId }).IsUnique();
}
}

View File

@@ -0,0 +1,149 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using ScadaLink.Commons.Entities.Templates;
namespace ScadaLink.ConfigurationDatabase.Configurations;
public class TemplateConfiguration : IEntityTypeConfiguration<Template>
{
public void Configure(EntityTypeBuilder<Template> builder)
{
builder.HasKey(t => t.Id);
builder.Property(t => t.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(t => t.Description)
.HasMaxLength(2000);
builder.HasIndex(t => t.Name).IsUnique();
// Self-referencing parent template (inheritance)
builder.HasOne<Template>()
.WithMany()
.HasForeignKey(t => t.ParentTemplateId)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);
builder.HasMany(t => t.Attributes)
.WithOne()
.HasForeignKey(a => a.TemplateId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(t => t.Alarms)
.WithOne()
.HasForeignKey(a => a.TemplateId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(t => t.Scripts)
.WithOne()
.HasForeignKey(s => s.TemplateId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(t => t.Compositions)
.WithOne()
.HasForeignKey(c => c.TemplateId)
.OnDelete(DeleteBehavior.Cascade);
}
}
public class TemplateAttributeConfiguration : IEntityTypeConfiguration<TemplateAttribute>
{
public void Configure(EntityTypeBuilder<TemplateAttribute> builder)
{
builder.HasKey(a => a.Id);
builder.Property(a => a.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(a => a.Value)
.HasMaxLength(4000);
builder.Property(a => a.Description)
.HasMaxLength(2000);
builder.Property(a => a.DataSourceReference)
.HasMaxLength(500);
builder.Property(a => a.DataType)
.HasConversion<string>()
.HasMaxLength(50);
builder.HasIndex(a => new { a.TemplateId, a.Name }).IsUnique();
}
}
public class TemplateAlarmConfiguration : IEntityTypeConfiguration<TemplateAlarm>
{
public void Configure(EntityTypeBuilder<TemplateAlarm> builder)
{
builder.HasKey(a => a.Id);
builder.Property(a => a.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(a => a.Description)
.HasMaxLength(2000);
builder.Property(a => a.TriggerType)
.HasConversion<string>()
.HasMaxLength(50);
builder.Property(a => a.TriggerConfiguration)
.HasMaxLength(4000);
builder.HasIndex(a => new { a.TemplateId, a.Name }).IsUnique();
}
}
public class TemplateScriptConfiguration : IEntityTypeConfiguration<TemplateScript>
{
public void Configure(EntityTypeBuilder<TemplateScript> builder)
{
builder.HasKey(s => s.Id);
builder.Property(s => s.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(s => s.Code)
.IsRequired();
builder.Property(s => s.TriggerType)
.HasMaxLength(50);
builder.Property(s => s.TriggerConfiguration)
.HasMaxLength(4000);
builder.Property(s => s.ParameterDefinitions)
.HasMaxLength(4000);
builder.Property(s => s.ReturnDefinition)
.HasMaxLength(4000);
builder.HasIndex(s => new { s.TemplateId, s.Name }).IsUnique();
}
}
public class TemplateCompositionConfiguration : IEntityTypeConfiguration<TemplateComposition>
{
public void Configure(EntityTypeBuilder<TemplateComposition> builder)
{
builder.HasKey(c => c.Id);
builder.Property(c => c.InstanceName)
.IsRequired()
.HasMaxLength(200);
// The composed template reference
builder.HasOne<Template>()
.WithMany()
.HasForeignKey(c => c.ComposedTemplateId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(c => new { c.TemplateId, c.InstanceName }).IsUnique();
}
}

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
namespace ScadaLink.ConfigurationDatabase;
/// <summary>
/// Factory for creating DbContext instances at design time (used by dotnet ef tooling).
/// Reads connection string from Host's appsettings.Central.json.
/// </summary>
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ScadaLinkDbContext>
{
public ScadaLinkDbContext CreateDbContext(string[] args)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "..", "ScadaLink.Host"))
.AddJsonFile("appsettings.json", optional: true)
.AddJsonFile("appsettings.Central.json", optional: true)
.Build();
var connectionString = configuration["ScadaLink:Database:ConfigurationDb"]
?? "Server=localhost,1433;Database=ScadaLink_Config;User Id=sa;Password=YourPassword;TrustServerCertificate=True";
var optionsBuilder = new DbContextOptionsBuilder<ScadaLinkDbContext>();
optionsBuilder.UseSqlServer(connectionString);
return new ScadaLinkDbContext(optionsBuilder.Options);
}
}

View File

@@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace ScadaLink.ConfigurationDatabase;
/// <summary>
/// Provides environment-aware migration behavior for the ScadaLink configuration database.
/// </summary>
public static class MigrationHelper
{
/// <summary>
/// Applies pending migrations (development mode) or validates schema version (production mode).
/// </summary>
/// <param name="dbContext">The database context to migrate or validate.</param>
/// <param name="isDevelopment">When true, auto-applies migrations. When false, validates schema version matches.</param>
/// <param name="cancellationToken">Cancellation token.</param>
public static async Task ApplyOrValidateMigrationsAsync(
ScadaLinkDbContext dbContext,
bool isDevelopment,
CancellationToken cancellationToken = default)
{
if (isDevelopment)
{
await dbContext.Database.MigrateAsync(cancellationToken);
}
else
{
var pendingMigrations = await dbContext.Database.GetPendingMigrationsAsync(cancellationToken);
var pending = pendingMigrations.ToList();
if (pending.Count > 0)
{
throw new InvalidOperationException(
$"Database schema is out of date. {pending.Count} pending migration(s): {string.Join(", ", pending)}. " +
"Apply migrations using 'dotnet ef database update' or the generated SQL scripts before starting in production mode.");
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,883 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ScadaLink.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ApiKeys",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
KeyValue = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
IsEnabled = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiKeys", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ApiMethods",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Script = table.Column<string>(type: "nvarchar(max)", nullable: false),
ApprovedApiKeyIds = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ParameterDefinitions = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ReturnDefinition = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
TimeoutSeconds = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiMethods", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AuditLogEntries",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
User = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Action = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
EntityType = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
EntityId = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
EntityName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
AfterStateJson = table.Column<string>(type: "nvarchar(max)", nullable: true),
Timestamp = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AuditLogEntries", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DatabaseConnectionDefinitions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
ConnectionString = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: false),
MaxRetries = table.Column<int>(type: "int", nullable: false),
RetryDelay = table.Column<TimeSpan>(type: "time", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DatabaseConnectionDefinitions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "DataConnections",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Protocol = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Configuration = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_DataConnections", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ExternalSystemDefinitions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
EndpointUrl = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false),
AuthType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
AuthConfiguration = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
MaxRetries = table.Column<int>(type: "int", nullable: false),
RetryDelay = table.Column<TimeSpan>(type: "time", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ExternalSystemDefinitions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "LdapGroupMappings",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
LdapGroupName = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
Role = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_LdapGroupMappings", x => x.Id);
});
migrationBuilder.CreateTable(
name: "NotificationLists",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_NotificationLists", x => x.Id);
});
migrationBuilder.CreateTable(
name: "SharedScripts",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Code = table.Column<string>(type: "nvarchar(max)", nullable: false),
ParameterDefinitions = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ReturnDefinition = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_SharedScripts", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Sites",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
SiteIdentifier = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Sites", x => x.Id);
});
migrationBuilder.CreateTable(
name: "SmtpConfigurations",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Host = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
Port = table.Column<int>(type: "int", nullable: false),
AuthType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Credentials = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
TlsMode = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
FromAddress = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
ConnectionTimeoutSeconds = table.Column<int>(type: "int", nullable: false),
MaxConcurrentConnections = table.Column<int>(type: "int", nullable: false),
MaxRetries = table.Column<int>(type: "int", nullable: false),
RetryDelay = table.Column<TimeSpan>(type: "time", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SmtpConfigurations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "SystemArtifactDeploymentRecords",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ArtifactType = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
DeployedBy = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
DeployedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
PerSiteStatus = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_SystemArtifactDeploymentRecords", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Templates",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
ParentTemplateId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Templates", x => x.Id);
table.ForeignKey(
name: "FK_Templates_Templates_ParentTemplateId",
column: x => x.ParentTemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "ExternalSystemMethods",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ExternalSystemDefinitionId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
HttpMethod = table.Column<string>(type: "nvarchar(10)", maxLength: 10, nullable: false),
Path = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false),
ParameterDefinitions = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ReturnDefinition = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ExternalSystemMethods", x => x.Id);
table.ForeignKey(
name: "FK_ExternalSystemMethods_ExternalSystemDefinitions_ExternalSystemDefinitionId",
column: x => x.ExternalSystemDefinitionId,
principalTable: "ExternalSystemDefinitions",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "NotificationRecipients",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
NotificationListId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
EmailAddress = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_NotificationRecipients", x => x.Id);
table.ForeignKey(
name: "FK_NotificationRecipients_NotificationLists_NotificationListId",
column: x => x.NotificationListId,
principalTable: "NotificationLists",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Areas",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
SiteId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
ParentAreaId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Areas", x => x.Id);
table.ForeignKey(
name: "FK_Areas_Areas_ParentAreaId",
column: x => x.ParentAreaId,
principalTable: "Areas",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Areas_Sites_SiteId",
column: x => x.SiteId,
principalTable: "Sites",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "SiteDataConnectionAssignments",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
SiteId = table.Column<int>(type: "int", nullable: false),
DataConnectionId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SiteDataConnectionAssignments", x => x.Id);
table.ForeignKey(
name: "FK_SiteDataConnectionAssignments_DataConnections_DataConnectionId",
column: x => x.DataConnectionId,
principalTable: "DataConnections",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_SiteDataConnectionAssignments_Sites_SiteId",
column: x => x.SiteId,
principalTable: "Sites",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "SiteScopeRules",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
LdapGroupMappingId = table.Column<int>(type: "int", nullable: false),
SiteId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SiteScopeRules", x => x.Id);
table.ForeignKey(
name: "FK_SiteScopeRules_LdapGroupMappings_LdapGroupMappingId",
column: x => x.LdapGroupMappingId,
principalTable: "LdapGroupMappings",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_SiteScopeRules_Sites_SiteId",
column: x => x.SiteId,
principalTable: "Sites",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "TemplateAlarms",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TemplateId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
PriorityLevel = table.Column<int>(type: "int", nullable: false),
IsLocked = table.Column<bool>(type: "bit", nullable: false),
TriggerType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
TriggerConfiguration = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
OnTriggerScriptId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TemplateAlarms", x => x.Id);
table.ForeignKey(
name: "FK_TemplateAlarms_Templates_TemplateId",
column: x => x.TemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "TemplateAttributes",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TemplateId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Value = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
DataType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
IsLocked = table.Column<bool>(type: "bit", nullable: false),
Description = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
DataSourceReference = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TemplateAttributes", x => x.Id);
table.ForeignKey(
name: "FK_TemplateAttributes_Templates_TemplateId",
column: x => x.TemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "TemplateCompositions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TemplateId = table.Column<int>(type: "int", nullable: false),
ComposedTemplateId = table.Column<int>(type: "int", nullable: false),
InstanceName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TemplateCompositions", x => x.Id);
table.ForeignKey(
name: "FK_TemplateCompositions_Templates_ComposedTemplateId",
column: x => x.ComposedTemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_TemplateCompositions_Templates_TemplateId",
column: x => x.TemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "TemplateScripts",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TemplateId = table.Column<int>(type: "int", nullable: false),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
IsLocked = table.Column<bool>(type: "bit", nullable: false),
Code = table.Column<string>(type: "nvarchar(max)", nullable: false),
TriggerType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
TriggerConfiguration = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ParameterDefinitions = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
ReturnDefinition = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true),
MinTimeBetweenRuns = table.Column<TimeSpan>(type: "time", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TemplateScripts", x => x.Id);
table.ForeignKey(
name: "FK_TemplateScripts_Templates_TemplateId",
column: x => x.TemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Instances",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TemplateId = table.Column<int>(type: "int", nullable: false),
SiteId = table.Column<int>(type: "int", nullable: false),
AreaId = table.Column<int>(type: "int", nullable: true),
UniqueName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
State = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Instances", x => x.Id);
table.ForeignKey(
name: "FK_Instances_Areas_AreaId",
column: x => x.AreaId,
principalTable: "Areas",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_Instances_Sites_SiteId",
column: x => x.SiteId,
principalTable: "Sites",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Instances_Templates_TemplateId",
column: x => x.TemplateId,
principalTable: "Templates",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "DeploymentRecords",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
InstanceId = table.Column<int>(type: "int", nullable: false),
Status = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
DeploymentId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
RevisionHash = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
DeployedBy = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
DeployedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
CompletedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
RowVersion = table.Column<byte[]>(type: "rowversion", rowVersion: true, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_DeploymentRecords", x => x.Id);
table.ForeignKey(
name: "FK_DeploymentRecords_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "InstanceAttributeOverrides",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
InstanceId = table.Column<int>(type: "int", nullable: false),
AttributeName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
OverrideValue = table.Column<string>(type: "nvarchar(4000)", maxLength: 4000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_InstanceAttributeOverrides", x => x.Id);
table.ForeignKey(
name: "FK_InstanceAttributeOverrides_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "InstanceConnectionBindings",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
InstanceId = table.Column<int>(type: "int", nullable: false),
AttributeName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
DataConnectionId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_InstanceConnectionBindings", x => x.Id);
table.ForeignKey(
name: "FK_InstanceConnectionBindings_DataConnections_DataConnectionId",
column: x => x.DataConnectionId,
principalTable: "DataConnections",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_InstanceConnectionBindings_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ApiKeys_KeyValue",
table: "ApiKeys",
column: "KeyValue",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ApiKeys_Name",
table: "ApiKeys",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ApiMethods_Name",
table: "ApiMethods",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Areas_ParentAreaId",
table: "Areas",
column: "ParentAreaId");
migrationBuilder.CreateIndex(
name: "IX_Areas_SiteId_ParentAreaId_Name",
table: "Areas",
columns: new[] { "SiteId", "ParentAreaId", "Name" },
unique: true,
filter: "[ParentAreaId] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_AuditLogEntries_Action",
table: "AuditLogEntries",
column: "Action");
migrationBuilder.CreateIndex(
name: "IX_AuditLogEntries_EntityId",
table: "AuditLogEntries",
column: "EntityId");
migrationBuilder.CreateIndex(
name: "IX_AuditLogEntries_EntityType",
table: "AuditLogEntries",
column: "EntityType");
migrationBuilder.CreateIndex(
name: "IX_AuditLogEntries_Timestamp",
table: "AuditLogEntries",
column: "Timestamp");
migrationBuilder.CreateIndex(
name: "IX_AuditLogEntries_User",
table: "AuditLogEntries",
column: "User");
migrationBuilder.CreateIndex(
name: "IX_DatabaseConnectionDefinitions_Name",
table: "DatabaseConnectionDefinitions",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DataConnections_Name",
table: "DataConnections",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeploymentRecords_DeployedAt",
table: "DeploymentRecords",
column: "DeployedAt");
migrationBuilder.CreateIndex(
name: "IX_DeploymentRecords_DeploymentId",
table: "DeploymentRecords",
column: "DeploymentId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_DeploymentRecords_InstanceId",
table: "DeploymentRecords",
column: "InstanceId");
migrationBuilder.CreateIndex(
name: "IX_ExternalSystemDefinitions_Name",
table: "ExternalSystemDefinitions",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ExternalSystemMethods_ExternalSystemDefinitionId_Name",
table: "ExternalSystemMethods",
columns: new[] { "ExternalSystemDefinitionId", "Name" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_InstanceAttributeOverrides_InstanceId_AttributeName",
table: "InstanceAttributeOverrides",
columns: new[] { "InstanceId", "AttributeName" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_InstanceConnectionBindings_DataConnectionId",
table: "InstanceConnectionBindings",
column: "DataConnectionId");
migrationBuilder.CreateIndex(
name: "IX_InstanceConnectionBindings_InstanceId_AttributeName",
table: "InstanceConnectionBindings",
columns: new[] { "InstanceId", "AttributeName" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Instances_AreaId",
table: "Instances",
column: "AreaId");
migrationBuilder.CreateIndex(
name: "IX_Instances_SiteId_UniqueName",
table: "Instances",
columns: new[] { "SiteId", "UniqueName" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Instances_TemplateId",
table: "Instances",
column: "TemplateId");
migrationBuilder.CreateIndex(
name: "IX_LdapGroupMappings_LdapGroupName",
table: "LdapGroupMappings",
column: "LdapGroupName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_NotificationLists_Name",
table: "NotificationLists",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_NotificationRecipients_NotificationListId",
table: "NotificationRecipients",
column: "NotificationListId");
migrationBuilder.CreateIndex(
name: "IX_SharedScripts_Name",
table: "SharedScripts",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_SiteDataConnectionAssignments_DataConnectionId",
table: "SiteDataConnectionAssignments",
column: "DataConnectionId");
migrationBuilder.CreateIndex(
name: "IX_SiteDataConnectionAssignments_SiteId_DataConnectionId",
table: "SiteDataConnectionAssignments",
columns: new[] { "SiteId", "DataConnectionId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Sites_Name",
table: "Sites",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Sites_SiteIdentifier",
table: "Sites",
column: "SiteIdentifier",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_SiteScopeRules_LdapGroupMappingId_SiteId",
table: "SiteScopeRules",
columns: new[] { "LdapGroupMappingId", "SiteId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_SiteScopeRules_SiteId",
table: "SiteScopeRules",
column: "SiteId");
migrationBuilder.CreateIndex(
name: "IX_SystemArtifactDeploymentRecords_DeployedAt",
table: "SystemArtifactDeploymentRecords",
column: "DeployedAt");
migrationBuilder.CreateIndex(
name: "IX_TemplateAlarms_TemplateId_Name",
table: "TemplateAlarms",
columns: new[] { "TemplateId", "Name" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_TemplateAttributes_TemplateId_Name",
table: "TemplateAttributes",
columns: new[] { "TemplateId", "Name" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_TemplateCompositions_ComposedTemplateId",
table: "TemplateCompositions",
column: "ComposedTemplateId");
migrationBuilder.CreateIndex(
name: "IX_TemplateCompositions_TemplateId_InstanceName",
table: "TemplateCompositions",
columns: new[] { "TemplateId", "InstanceName" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Templates_Name",
table: "Templates",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Templates_ParentTemplateId",
table: "Templates",
column: "ParentTemplateId");
migrationBuilder.CreateIndex(
name: "IX_TemplateScripts_TemplateId_Name",
table: "TemplateScripts",
columns: new[] { "TemplateId", "Name" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ApiKeys");
migrationBuilder.DropTable(
name: "ApiMethods");
migrationBuilder.DropTable(
name: "AuditLogEntries");
migrationBuilder.DropTable(
name: "DatabaseConnectionDefinitions");
migrationBuilder.DropTable(
name: "DeploymentRecords");
migrationBuilder.DropTable(
name: "ExternalSystemMethods");
migrationBuilder.DropTable(
name: "InstanceAttributeOverrides");
migrationBuilder.DropTable(
name: "InstanceConnectionBindings");
migrationBuilder.DropTable(
name: "NotificationRecipients");
migrationBuilder.DropTable(
name: "SharedScripts");
migrationBuilder.DropTable(
name: "SiteDataConnectionAssignments");
migrationBuilder.DropTable(
name: "SiteScopeRules");
migrationBuilder.DropTable(
name: "SmtpConfigurations");
migrationBuilder.DropTable(
name: "SystemArtifactDeploymentRecords");
migrationBuilder.DropTable(
name: "TemplateAlarms");
migrationBuilder.DropTable(
name: "TemplateAttributes");
migrationBuilder.DropTable(
name: "TemplateCompositions");
migrationBuilder.DropTable(
name: "TemplateScripts");
migrationBuilder.DropTable(
name: "ExternalSystemDefinitions");
migrationBuilder.DropTable(
name: "Instances");
migrationBuilder.DropTable(
name: "NotificationLists");
migrationBuilder.DropTable(
name: "DataConnections");
migrationBuilder.DropTable(
name: "LdapGroupMappings");
migrationBuilder.DropTable(
name: "Areas");
migrationBuilder.DropTable(
name: "Templates");
migrationBuilder.DropTable(
name: "Sites");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.5" />
</ItemGroup>

View File

@@ -0,0 +1,71 @@
using Microsoft.EntityFrameworkCore;
using ScadaLink.Commons.Entities.Audit;
using ScadaLink.Commons.Entities.Deployment;
using ScadaLink.Commons.Entities.ExternalSystems;
using ScadaLink.Commons.Entities.InboundApi;
using ScadaLink.Commons.Entities.Instances;
using ScadaLink.Commons.Entities.Notifications;
using ScadaLink.Commons.Entities.Scripts;
using ScadaLink.Commons.Entities.Security;
using ScadaLink.Commons.Entities.Sites;
using ScadaLink.Commons.Entities.Templates;
namespace ScadaLink.ConfigurationDatabase;
public class ScadaLinkDbContext : DbContext
{
public ScadaLinkDbContext(DbContextOptions<ScadaLinkDbContext> options) : base(options)
{
}
// Templates
public DbSet<Template> Templates => Set<Template>();
public DbSet<TemplateAttribute> TemplateAttributes => Set<TemplateAttribute>();
public DbSet<TemplateAlarm> TemplateAlarms => Set<TemplateAlarm>();
public DbSet<TemplateScript> TemplateScripts => Set<TemplateScript>();
public DbSet<TemplateComposition> TemplateCompositions => Set<TemplateComposition>();
// Instances
public DbSet<Instance> Instances => Set<Instance>();
public DbSet<InstanceAttributeOverride> InstanceAttributeOverrides => Set<InstanceAttributeOverride>();
public DbSet<InstanceConnectionBinding> InstanceConnectionBindings => Set<InstanceConnectionBinding>();
public DbSet<Area> Areas => Set<Area>();
// Sites
public DbSet<Site> Sites => Set<Site>();
public DbSet<DataConnection> DataConnections => Set<DataConnection>();
public DbSet<SiteDataConnectionAssignment> SiteDataConnectionAssignments => Set<SiteDataConnectionAssignment>();
// Deployment
public DbSet<DeploymentRecord> DeploymentRecords => Set<DeploymentRecord>();
public DbSet<SystemArtifactDeploymentRecord> SystemArtifactDeploymentRecords => Set<SystemArtifactDeploymentRecord>();
// External Systems
public DbSet<ExternalSystemDefinition> ExternalSystemDefinitions => Set<ExternalSystemDefinition>();
public DbSet<ExternalSystemMethod> ExternalSystemMethods => Set<ExternalSystemMethod>();
public DbSet<DatabaseConnectionDefinition> DatabaseConnectionDefinitions => Set<DatabaseConnectionDefinition>();
// Notifications
public DbSet<NotificationList> NotificationLists => Set<NotificationList>();
public DbSet<NotificationRecipient> NotificationRecipients => Set<NotificationRecipient>();
public DbSet<SmtpConfiguration> SmtpConfigurations => Set<SmtpConfiguration>();
// Scripts
public DbSet<SharedScript> SharedScripts => Set<SharedScript>();
// Security
public DbSet<LdapGroupMapping> LdapGroupMappings => Set<LdapGroupMapping>();
public DbSet<SiteScopeRule> SiteScopeRules => Set<SiteScopeRule>();
// Inbound API
public DbSet<ApiKey> ApiKeys => Set<ApiKey>();
public DbSet<ApiMethod> ApiMethods => Set<ApiMethod>();
// Audit
public DbSet<AuditLogEntry> AuditLogEntries => Set<AuditLogEntry>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ScadaLinkDbContext).Assembly);
}
}

View File

@@ -1,12 +1,29 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace ScadaLink.ConfigurationDatabase;
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registers the ScadaLinkDbContext with the provided SQL Server connection string.
/// </summary>
public static IServiceCollection AddConfigurationDatabase(this IServiceCollection services, string connectionString)
{
services.AddDbContext<ScadaLinkDbContext>(options =>
options.UseSqlServer(connectionString));
return services;
}
/// <summary>
/// Registers the ScadaLinkDbContext with no connection string (for backward compatibility / Phase 0 stubs).
/// This overload is a no-op placeholder; callers should migrate to the overload that accepts a connection string.
/// </summary>
public static IServiceCollection AddConfigurationDatabase(this IServiceCollection services)
{
// Phase 0: skeleton only
// Retained for backward compatibility during migration.
// Site nodes do not use the configuration database, so this is intentionally a no-op.
return services;
}
}

View File

@@ -43,7 +43,10 @@ if (role.Equals("Central", StringComparison.OrdinalIgnoreCase))
builder.Services.AddSecurity();
builder.Services.AddCentralUI();
builder.Services.AddInboundAPI();
builder.Services.AddConfigurationDatabase();
var configDbConnectionString = configuration["ScadaLink:Database:ConfigurationDb"]
?? throw new InvalidOperationException("ScadaLink:Database:ConfigurationDb connection string is required for Central role.");
builder.Services.AddConfigurationDatabase(configDbConnectionString);
// Options binding
BindSharedOptions(builder.Services, builder.Configuration);
@@ -51,6 +54,18 @@ if (role.Equals("Central", StringComparison.OrdinalIgnoreCase))
builder.Services.Configure<InboundApiOptions>(builder.Configuration.GetSection("ScadaLink:InboundApi"));
var app = builder.Build();
// Apply or validate database migrations (skip when running in test harness)
if (!string.Equals(configuration["ScadaLink:Database:SkipMigrations"], "true", StringComparison.OrdinalIgnoreCase))
{
var isDevelopment = app.Environment.IsDevelopment();
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<ScadaLinkDbContext>();
await MigrationHelper.ApplyOrValidateMigrationsAsync(dbContext, isDevelopment);
}
}
app.MapCentralUI();
app.MapInboundAPI();
await app.RunAsync();

View File

@@ -7,6 +7,13 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../ScadaLink.Commons/ScadaLink.Commons.csproj" />
<ProjectReference Include="../ScadaLink.TemplateEngine/ScadaLink.TemplateEngine.csproj" />