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:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/ScadaLink.ConfigurationDatabase/MigrationHelper.cs
Normal file
41
src/ScadaLink.ConfigurationDatabase/MigrationHelper.cs
Normal 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1144
src/ScadaLink.ConfigurationDatabase/Migrations/20260316231104_InitialCreate.Designer.cs
generated
Normal file
1144
src/ScadaLink.ConfigurationDatabase/Migrations/20260316231104_InitialCreate.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -8,6 +8,13 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<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.DependencyInjection.Abstractions" Version="10.0.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
71
src/ScadaLink.ConfigurationDatabase/ScadaLinkDbContext.cs
Normal file
71
src/ScadaLink.ConfigurationDatabase/ScadaLinkDbContext.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace ScadaLink.ConfigurationDatabase;
|
namespace ScadaLink.ConfigurationDatabase;
|
||||||
|
|
||||||
public static class ServiceCollectionExtensions
|
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)
|
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;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ if (role.Equals("Central", StringComparison.OrdinalIgnoreCase))
|
|||||||
builder.Services.AddSecurity();
|
builder.Services.AddSecurity();
|
||||||
builder.Services.AddCentralUI();
|
builder.Services.AddCentralUI();
|
||||||
builder.Services.AddInboundAPI();
|
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
|
// Options binding
|
||||||
BindSharedOptions(builder.Services, builder.Configuration);
|
BindSharedOptions(builder.Services, builder.Configuration);
|
||||||
@@ -51,6 +54,18 @@ if (role.Equals("Central", StringComparison.OrdinalIgnoreCase))
|
|||||||
builder.Services.Configure<InboundApiOptions>(builder.Configuration.GetSection("ScadaLink:InboundApi"));
|
builder.Services.Configure<InboundApiOptions>(builder.Configuration.GetSection("ScadaLink:InboundApi"));
|
||||||
|
|
||||||
var app = builder.Build();
|
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.MapCentralUI();
|
||||||
app.MapInboundAPI();
|
app.MapInboundAPI();
|
||||||
await app.RunAsync();
|
await app.RunAsync();
|
||||||
|
|||||||
@@ -7,6 +7,13 @@
|
|||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</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>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../ScadaLink.Commons/ScadaLink.Commons.csproj" />
|
<ProjectReference Include="../ScadaLink.Commons/ScadaLink.Commons.csproj" />
|
||||||
<ProjectReference Include="../ScadaLink.TemplateEngine/ScadaLink.TemplateEngine.csproj" />
|
<ProjectReference Include="../ScadaLink.TemplateEngine/ScadaLink.TemplateEngine.csproj" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageReference Include="xunit" Version="2.9.3" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../../src/ScadaLink.ConfigurationDatabase/ScadaLink.ConfigurationDatabase.csproj" />
|
<ProjectReference Include="../../src/ScadaLink.ConfigurationDatabase/ScadaLink.ConfigurationDatabase.csproj" />
|
||||||
|
<ProjectReference Include="../../src/ScadaLink.Commons/ScadaLink.Commons.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,10 +1,434 @@
|
|||||||
namespace ScadaLink.ConfigurationDatabase.Tests;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
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;
|
||||||
|
using ScadaLink.Commons.Types.Enums;
|
||||||
|
using ScadaLink.ConfigurationDatabase;
|
||||||
|
|
||||||
public class UnitTest1
|
namespace ScadaLink.ConfigurationDatabase.Tests;
|
||||||
|
|
||||||
|
public class DbContextTests : IDisposable
|
||||||
{
|
{
|
||||||
[Fact]
|
private readonly ScadaLinkDbContext _context;
|
||||||
public void Test1()
|
|
||||||
{
|
|
||||||
|
|
||||||
|
public DbContextTests()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<ScadaLinkDbContext>()
|
||||||
|
.UseSqlite("DataSource=:memory:")
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
_context = new ScadaLinkDbContext(options);
|
||||||
|
_context.Database.OpenConnection();
|
||||||
|
_context.Database.EnsureCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_context.Database.CloseConnection();
|
||||||
|
_context.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Schema_CreatesAllTables()
|
||||||
|
{
|
||||||
|
// Verify all DbSet tables exist by checking we can query them without error
|
||||||
|
Assert.NotNull(_context.Templates);
|
||||||
|
Assert.NotNull(_context.TemplateAttributes);
|
||||||
|
Assert.NotNull(_context.TemplateAlarms);
|
||||||
|
Assert.NotNull(_context.TemplateScripts);
|
||||||
|
Assert.NotNull(_context.TemplateCompositions);
|
||||||
|
Assert.NotNull(_context.Instances);
|
||||||
|
Assert.NotNull(_context.InstanceAttributeOverrides);
|
||||||
|
Assert.NotNull(_context.InstanceConnectionBindings);
|
||||||
|
Assert.NotNull(_context.Areas);
|
||||||
|
Assert.NotNull(_context.Sites);
|
||||||
|
Assert.NotNull(_context.DataConnections);
|
||||||
|
Assert.NotNull(_context.SiteDataConnectionAssignments);
|
||||||
|
Assert.NotNull(_context.DeploymentRecords);
|
||||||
|
Assert.NotNull(_context.SystemArtifactDeploymentRecords);
|
||||||
|
Assert.NotNull(_context.ExternalSystemDefinitions);
|
||||||
|
Assert.NotNull(_context.ExternalSystemMethods);
|
||||||
|
Assert.NotNull(_context.DatabaseConnectionDefinitions);
|
||||||
|
Assert.NotNull(_context.NotificationLists);
|
||||||
|
Assert.NotNull(_context.NotificationRecipients);
|
||||||
|
Assert.NotNull(_context.SmtpConfigurations);
|
||||||
|
Assert.NotNull(_context.SharedScripts);
|
||||||
|
Assert.NotNull(_context.LdapGroupMappings);
|
||||||
|
Assert.NotNull(_context.SiteScopeRules);
|
||||||
|
Assert.NotNull(_context.ApiKeys);
|
||||||
|
Assert.NotNull(_context.ApiMethods);
|
||||||
|
Assert.NotNull(_context.AuditLogEntries);
|
||||||
|
|
||||||
|
// Verify we can enumerate all tables (schema is valid)
|
||||||
|
Assert.Empty(_context.Templates.ToList());
|
||||||
|
Assert.Empty(_context.Sites.ToList());
|
||||||
|
Assert.Empty(_context.Instances.ToList());
|
||||||
|
Assert.Empty(_context.AuditLogEntries.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Template_WithChildren_CascadeCreated()
|
||||||
|
{
|
||||||
|
var template = new Template("TestTemplate")
|
||||||
|
{
|
||||||
|
Description = "A test template"
|
||||||
|
};
|
||||||
|
template.Attributes.Add(new TemplateAttribute("Attr1") { DataType = DataType.Int32 });
|
||||||
|
template.Alarms.Add(new TemplateAlarm("Alarm1") { TriggerType = AlarmTriggerType.ValueMatch, PriorityLevel = 1 });
|
||||||
|
template.Scripts.Add(new TemplateScript("Script1", "return 42;"));
|
||||||
|
|
||||||
|
_context.Templates.Add(template);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var loaded = _context.Templates
|
||||||
|
.Include(t => t.Attributes)
|
||||||
|
.Include(t => t.Alarms)
|
||||||
|
.Include(t => t.Scripts)
|
||||||
|
.Single(t => t.Name == "TestTemplate");
|
||||||
|
|
||||||
|
Assert.Single(loaded.Attributes);
|
||||||
|
Assert.Single(loaded.Alarms);
|
||||||
|
Assert.Single(loaded.Scripts);
|
||||||
|
Assert.Equal("Attr1", loaded.Attributes.First().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Template_Inheritance_SelfReference()
|
||||||
|
{
|
||||||
|
var parent = new Template("ParentTemplate");
|
||||||
|
_context.Templates.Add(parent);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var child = new Template("ChildTemplate") { ParentTemplateId = parent.Id };
|
||||||
|
_context.Templates.Add(child);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var loaded = _context.Templates.Single(t => t.Name == "ChildTemplate");
|
||||||
|
Assert.Equal(parent.Id, loaded.ParentTemplateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Template_Composition_CreatesRelationship()
|
||||||
|
{
|
||||||
|
var composedTemplate = new Template("ComposedTemplate");
|
||||||
|
var parentTemplate = new Template("ParentTemplate");
|
||||||
|
_context.Templates.AddRange(composedTemplate, parentTemplate);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
parentTemplate.Compositions.Add(new TemplateComposition("Module1") { ComposedTemplateId = composedTemplate.Id });
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var loaded = _context.Templates
|
||||||
|
.Include(t => t.Compositions)
|
||||||
|
.Single(t => t.Name == "ParentTemplate");
|
||||||
|
|
||||||
|
Assert.Single(loaded.Compositions);
|
||||||
|
Assert.Equal(composedTemplate.Id, loaded.Compositions.First().ComposedTemplateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Instance_WithOverridesAndBindings()
|
||||||
|
{
|
||||||
|
var site = new Site("Site1", "SITE-001");
|
||||||
|
var template = new Template("Template1");
|
||||||
|
var dataConn = new DataConnection("OpcConn", "OpcUa");
|
||||||
|
_context.Sites.Add(site);
|
||||||
|
_context.Templates.Add(template);
|
||||||
|
_context.DataConnections.Add(dataConn);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var instance = new Instance("Instance1")
|
||||||
|
{
|
||||||
|
TemplateId = template.Id,
|
||||||
|
SiteId = site.Id,
|
||||||
|
State = InstanceState.Enabled
|
||||||
|
};
|
||||||
|
instance.AttributeOverrides.Add(new InstanceAttributeOverride("Attr1") { OverrideValue = "42" });
|
||||||
|
instance.ConnectionBindings.Add(new InstanceConnectionBinding("TagPath") { DataConnectionId = dataConn.Id });
|
||||||
|
_context.Instances.Add(instance);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var loaded = _context.Instances
|
||||||
|
.Include(i => i.AttributeOverrides)
|
||||||
|
.Include(i => i.ConnectionBindings)
|
||||||
|
.Single(i => i.UniqueName == "Instance1");
|
||||||
|
|
||||||
|
Assert.Single(loaded.AttributeOverrides);
|
||||||
|
Assert.Single(loaded.ConnectionBindings);
|
||||||
|
Assert.Equal(InstanceState.Enabled, loaded.State);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DeploymentRecord_CreatesWithAllFields()
|
||||||
|
{
|
||||||
|
var site = new Site("Site1", "SITE-001");
|
||||||
|
var template = new Template("Template1");
|
||||||
|
_context.Sites.Add(site);
|
||||||
|
_context.Templates.Add(template);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var instance = new Instance("Instance1") { TemplateId = template.Id, SiteId = site.Id, State = InstanceState.Enabled };
|
||||||
|
_context.Instances.Add(instance);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var record = new DeploymentRecord("deploy-001", "admin")
|
||||||
|
{
|
||||||
|
InstanceId = instance.Id,
|
||||||
|
Status = DeploymentStatus.Success,
|
||||||
|
RevisionHash = "abc123",
|
||||||
|
DeployedAt = DateTimeOffset.UtcNow,
|
||||||
|
CompletedAt = DateTimeOffset.UtcNow
|
||||||
|
};
|
||||||
|
_context.DeploymentRecords.Add(record);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var loaded = _context.DeploymentRecords.Single(d => d.DeploymentId == "deploy-001");
|
||||||
|
Assert.Equal(DeploymentStatus.Success, loaded.Status);
|
||||||
|
Assert.Equal("abc123", loaded.RevisionHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AuditLogEntry_WritesAndQueries()
|
||||||
|
{
|
||||||
|
var entry = new AuditLogEntry("admin", "Create", "Template", "1", "TestTemplate")
|
||||||
|
{
|
||||||
|
Timestamp = DateTimeOffset.UtcNow,
|
||||||
|
AfterStateJson = "{\"name\":\"TestTemplate\"}"
|
||||||
|
};
|
||||||
|
_context.AuditLogEntries.Add(entry);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var loaded = _context.AuditLogEntries.Single(a => a.User == "admin");
|
||||||
|
Assert.Equal("Create", loaded.Action);
|
||||||
|
Assert.Equal("Template", loaded.EntityType);
|
||||||
|
Assert.NotNull(loaded.AfterStateJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExternalSystem_WithMethods()
|
||||||
|
{
|
||||||
|
var system = new ExternalSystemDefinition("ERP", "https://erp.example.com/api", "ApiKey")
|
||||||
|
{
|
||||||
|
MaxRetries = 3,
|
||||||
|
RetryDelay = TimeSpan.FromSeconds(5)
|
||||||
|
};
|
||||||
|
_context.ExternalSystemDefinitions.Add(system);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var method = new ExternalSystemMethod("GetOrder", "GET", "/orders/{id}")
|
||||||
|
{
|
||||||
|
ExternalSystemDefinitionId = system.Id
|
||||||
|
};
|
||||||
|
_context.ExternalSystemMethods.Add(method);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
Assert.Single(_context.ExternalSystemMethods.Where(m => m.ExternalSystemDefinitionId == system.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NotificationList_WithRecipients()
|
||||||
|
{
|
||||||
|
var list = new NotificationList("Operators");
|
||||||
|
list.Recipients.Add(new NotificationRecipient("John", "john@example.com"));
|
||||||
|
list.Recipients.Add(new NotificationRecipient("Jane", "jane@example.com"));
|
||||||
|
|
||||||
|
_context.NotificationLists.Add(list);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var loaded = _context.NotificationLists
|
||||||
|
.Include(n => n.Recipients)
|
||||||
|
.Single(n => n.Name == "Operators");
|
||||||
|
|
||||||
|
Assert.Equal(2, loaded.Recipients.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Security_LdapGroupMapping_WithSiteScopeRules()
|
||||||
|
{
|
||||||
|
var site = new Site("Site1", "SITE-001");
|
||||||
|
_context.Sites.Add(site);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var mapping = new LdapGroupMapping("CN=Admins,DC=example,DC=com", "Admin");
|
||||||
|
_context.LdapGroupMappings.Add(mapping);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var rule = new SiteScopeRule { LdapGroupMappingId = mapping.Id, SiteId = site.Id };
|
||||||
|
_context.SiteScopeRules.Add(rule);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
Assert.Single(_context.SiteScopeRules.Where(r => r.LdapGroupMappingId == mapping.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InboundApi_ApiKeyAndMethod()
|
||||||
|
{
|
||||||
|
var key = new ApiKey("TestKey", "sk-test-123") { IsEnabled = true };
|
||||||
|
var method = new ApiMethod("GetStatus", "return \"ok\";") { TimeoutSeconds = 30 };
|
||||||
|
|
||||||
|
_context.ApiKeys.Add(key);
|
||||||
|
_context.ApiMethods.Add(method);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
Assert.Single(_context.ApiKeys.Where(k => k.Name == "TestKey"));
|
||||||
|
Assert.Single(_context.ApiMethods.Where(m => m.Name == "GetStatus"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Area_HierarchyWorks()
|
||||||
|
{
|
||||||
|
var site = new Site("Site1", "SITE-001");
|
||||||
|
_context.Sites.Add(site);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var parentArea = new Area("Building A") { SiteId = site.Id };
|
||||||
|
_context.Areas.Add(parentArea);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var childArea = new Area("Floor 1") { SiteId = site.Id, ParentAreaId = parentArea.Id };
|
||||||
|
_context.Areas.Add(childArea);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var loaded = _context.Areas
|
||||||
|
.Include(a => a.Children)
|
||||||
|
.Single(a => a.Name == "Building A");
|
||||||
|
|
||||||
|
Assert.Single(loaded.Children);
|
||||||
|
Assert.Equal("Floor 1", loaded.Children.First().Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SiteDataConnectionAssignment_CreatesBothForeignKeys()
|
||||||
|
{
|
||||||
|
var site = new Site("Site1", "SITE-001");
|
||||||
|
var conn = new DataConnection("OpcConn", "OpcUa");
|
||||||
|
_context.Sites.Add(site);
|
||||||
|
_context.DataConnections.Add(conn);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
var assignment = new SiteDataConnectionAssignment { SiteId = site.Id, DataConnectionId = conn.Id };
|
||||||
|
_context.SiteDataConnectionAssignments.Add(assignment);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
Assert.Single(_context.SiteDataConnectionAssignments.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EnumProperties_StoredAsStrings()
|
||||||
|
{
|
||||||
|
var template = new Template("EnumTest");
|
||||||
|
template.Attributes.Add(new TemplateAttribute("Attr1") { DataType = DataType.Double });
|
||||||
|
template.Alarms.Add(new TemplateAlarm("Alarm1") { TriggerType = AlarmTriggerType.RangeViolation, PriorityLevel = 1 });
|
||||||
|
_context.Templates.Add(template);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
// Query using raw SQL to verify string storage
|
||||||
|
var attr = _context.TemplateAttributes.Single(a => a.Name == "Attr1");
|
||||||
|
Assert.Equal(DataType.Double, attr.DataType);
|
||||||
|
|
||||||
|
var alarm = _context.TemplateAlarms.Single(a => a.Name == "Alarm1");
|
||||||
|
Assert.Equal(AlarmTriggerType.RangeViolation, alarm.TriggerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UniqueConstraint_Template_Name_Enforced()
|
||||||
|
{
|
||||||
|
_context.Templates.Add(new Template("Unique"));
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
_context.Templates.Add(new Template("Unique"));
|
||||||
|
Assert.ThrowsAny<Exception>(() => _context.SaveChanges());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DateTimeOffset_MappedCorrectly()
|
||||||
|
{
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
var entry = new AuditLogEntry("user", "Test", "Entity", "1", "Name") { Timestamp = now };
|
||||||
|
_context.AuditLogEntries.Add(entry);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
_context.ChangeTracker.Clear();
|
||||||
|
var loaded = _context.AuditLogEntries.Single();
|
||||||
|
// SQLite has limited DateTimeOffset precision, but the round-trip should preserve the value within a second
|
||||||
|
Assert.True(Math.Abs((loaded.Timestamp - now).TotalSeconds) < 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ServiceRegistrationTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void AddConfigurationDatabase_WithConnectionString_RegistersDbContext()
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddConfigurationDatabase("DataSource=:memory:");
|
||||||
|
|
||||||
|
var provider = services.BuildServiceProvider();
|
||||||
|
var context = provider.GetService<ScadaLinkDbContext>();
|
||||||
|
|
||||||
|
Assert.NotNull(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddConfigurationDatabase_NoArgs_DoesNotThrow()
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddConfigurationDatabase();
|
||||||
|
|
||||||
|
// Should not register DbContext (no-op for backward compatibility)
|
||||||
|
var provider = services.BuildServiceProvider();
|
||||||
|
var context = provider.GetService<ScadaLinkDbContext>();
|
||||||
|
Assert.Null(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MigrationHelperTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly ScadaLinkDbContext _context;
|
||||||
|
|
||||||
|
public MigrationHelperTests()
|
||||||
|
{
|
||||||
|
// Use SQLite with PendingModelChangesWarning suppressed because the migration
|
||||||
|
// was generated for SQL Server and SQLite's model representation differs slightly.
|
||||||
|
var options = new DbContextOptionsBuilder<ScadaLinkDbContext>()
|
||||||
|
.UseSqlite("DataSource=:memory:")
|
||||||
|
.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning))
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
_context = new ScadaLinkDbContext(options);
|
||||||
|
_context.Database.OpenConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_context.Database.CloseConnection();
|
||||||
|
_context.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ApplyOrValidate_ProductionMode_WithPendingMigrations_Throws()
|
||||||
|
{
|
||||||
|
// Database has no schema yet, so pending migrations exist.
|
||||||
|
// The production path uses GetPendingMigrationsAsync which works cross-provider.
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
() => MigrationHelper.ApplyOrValidateMigrationsAsync(_context, isDevelopment: false));
|
||||||
|
|
||||||
|
Assert.Contains("pending migration", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MigrationExists_InitialCreate()
|
||||||
|
{
|
||||||
|
// Verify the InitialCreate migration is detected as pending
|
||||||
|
var pending = _context.Database.GetPendingMigrations().ToList();
|
||||||
|
Assert.Contains(pending, m => m.Contains("InitialCreate"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public class HostStartupTests : IDisposable
|
|||||||
.WithWebHostBuilder(builder =>
|
.WithWebHostBuilder(builder =>
|
||||||
{
|
{
|
||||||
builder.UseSetting("ScadaLink:Node:Role", "Central");
|
builder.UseSetting("ScadaLink:Node:Role", "Central");
|
||||||
|
builder.UseSetting("ScadaLink:Database:SkipMigrations", "true");
|
||||||
});
|
});
|
||||||
_disposables.Add(factory);
|
_disposables.Add(factory);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user