feat: wire site-local repos, remove config DB from Site, update artifact service

- SiteExternalSystemRepository and SiteNotificationRepository registered in Site DI
- Removed AddConfigurationDatabase from Site role in Program.cs
- Removed ConfigurationDb from appsettings.Site.json
- ArtifactDeploymentService collects all 6 artifact types including data connections and SMTP
This commit is contained in:
Joseph Doherty
2026-03-17 13:54:37 -04:00
parent 2f3e0ceecb
commit 3b22a8f0da
5 changed files with 92 additions and 12 deletions

View File

@@ -12,8 +12,8 @@ namespace ScadaLink.DeploymentManager;
/// <summary>
/// WP-7: System-wide artifact deployment.
/// Broadcasts artifacts (shared scripts, external systems, notification lists, DB connections)
/// to all sites with per-site tracking.
/// Broadcasts artifacts (shared scripts, external systems, notification lists, DB connections,
/// data connections, and SMTP configurations) to all sites with per-site tracking.
///
/// - Successful sites are NOT rolled back on other failures.
/// - Failed sites are retryable individually.
@@ -24,6 +24,9 @@ public class ArtifactDeploymentService
{
private readonly ISiteRepository _siteRepo;
private readonly IDeploymentManagerRepository _deploymentRepo;
private readonly ITemplateEngineRepository _templateRepo;
private readonly IExternalSystemRepository _externalSystemRepo;
private readonly INotificationRepository _notificationRepo;
private readonly CommunicationService _communicationService;
private readonly IAuditService _auditService;
private readonly DeploymentManagerOptions _options;
@@ -32,6 +35,9 @@ public class ArtifactDeploymentService
public ArtifactDeploymentService(
ISiteRepository siteRepo,
IDeploymentManagerRepository deploymentRepo,
ITemplateEngineRepository templateRepo,
IExternalSystemRepository externalSystemRepo,
INotificationRepository notificationRepo,
CommunicationService communicationService,
IAuditService auditService,
IOptions<DeploymentManagerOptions> options,
@@ -39,12 +45,80 @@ public class ArtifactDeploymentService
{
_siteRepo = siteRepo;
_deploymentRepo = deploymentRepo;
_templateRepo = templateRepo;
_externalSystemRepo = externalSystemRepo;
_notificationRepo = notificationRepo;
_communicationService = communicationService;
_auditService = auditService;
_options = options.Value;
_logger = logger;
}
/// <summary>
/// Collects all artifact types from repositories and builds a <see cref="DeployArtifactsCommand"/>.
/// </summary>
public async Task<DeployArtifactsCommand> BuildDeployArtifactsCommandAsync(
CancellationToken cancellationToken = default)
{
var sharedScripts = await _templateRepo.GetAllSharedScriptsAsync(cancellationToken);
var externalSystems = await _externalSystemRepo.GetAllExternalSystemsAsync(cancellationToken);
var dbConnections = await _externalSystemRepo.GetAllDatabaseConnectionsAsync(cancellationToken);
var notificationLists = await _notificationRepo.GetAllNotificationListsAsync(cancellationToken);
var dataConnections = await _siteRepo.GetAllDataConnectionsAsync(cancellationToken);
var smtpConfigurations = await _notificationRepo.GetAllSmtpConfigurationsAsync(cancellationToken);
// Map shared scripts
var scriptArtifacts = sharedScripts.Select(s =>
new SharedScriptArtifact(s.Name, s.Code, s.ParameterDefinitions, s.ReturnDefinition)).ToList();
// Map external systems (serialize methods per system)
var externalSystemArtifacts = new List<ExternalSystemArtifact>();
foreach (var es in externalSystems)
{
var methods = await _externalSystemRepo.GetMethodsByExternalSystemIdAsync(es.Id, cancellationToken);
var methodsJson = methods.Count > 0
? JsonSerializer.Serialize(methods.Select(m => new
{
m.Name,
m.HttpMethod,
m.Path,
m.ParameterDefinitions,
m.ReturnDefinition
}))
: null;
externalSystemArtifacts.Add(new ExternalSystemArtifact(
es.Name, es.EndpointUrl, es.AuthType, es.AuthConfiguration, methodsJson));
}
// Map database connections
var dbConnectionArtifacts = dbConnections.Select(d =>
new DatabaseConnectionArtifact(d.Name, d.ConnectionString, d.MaxRetries, d.RetryDelay)).ToList();
// Map notification lists
var notificationListArtifacts = notificationLists.Select(nl =>
new NotificationListArtifact(nl.Name, nl.Recipients.Select(r => r.EmailAddress).ToList())).ToList();
// Map data connections
var dataConnectionArtifacts = dataConnections.Select(dc =>
new DataConnectionArtifact(dc.Name, dc.Protocol, dc.Configuration)).ToList();
// Map SMTP configurations — use Host as the artifact name (matches SQLite PK on site)
var smtpArtifacts = smtpConfigurations.Select(smtp =>
new SmtpConfigurationArtifact(
$"{smtp.Host}:{smtp.Port}", smtp.Host, smtp.Port, smtp.AuthType, smtp.FromAddress,
smtp.Credentials, null, smtp.TlsMode)).ToList();
return new DeployArtifactsCommand(
Guid.NewGuid().ToString("N"),
scriptArtifacts,
externalSystemArtifacts,
dbConnectionArtifacts,
notificationListArtifacts,
dataConnectionArtifacts,
smtpArtifacts,
DateTimeOffset.UtcNow);
}
/// <summary>
/// Deploys artifacts to all sites. Returns per-site result matrix.
/// </summary>

View File

@@ -152,14 +152,8 @@ try
services.AddExternalSystemGateway();
services.AddNotificationService();
// Configuration database (read-only access for external system definitions, notification lists)
var configDbConnectionString = context.Configuration["ScadaLink:Database:ConfigurationDb"];
if (!string.IsNullOrWhiteSpace(configDbConnectionString))
{
services.AddConfigurationDatabase(configDbConnectionString);
}
// Site-only components — AddSiteRuntime registers SiteStorageService with SQLite path
// and site-local repository implementations (IExternalSystemRepository, INotificationRepository)
var siteDbPath = context.Configuration["ScadaLink:Database:SiteDbPath"] ?? "site.db";
services.AddSiteRuntime($"Data Source={siteDbPath}");
services.AddDataConnectionLayer();

View File

@@ -18,8 +18,7 @@
"MinNrOfMembers": 1
},
"Database": {
"SiteDbPath": "./data/scadalink.db",
"ConfigurationDb": "Server=localhost,1433;Database=ScadaLinkConfig;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true"
"SiteDbPath": "./data/scadalink.db"
},
"DataConnection": {
"ReconnectInterval": "00:00:05",

View File

@@ -1,6 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.SiteRuntime.Persistence;
using ScadaLink.SiteRuntime.Repositories;
using ScadaLink.SiteRuntime.Scripts;
namespace ScadaLink.SiteRuntime;
@@ -38,6 +40,10 @@ public static class ServiceCollectionExtensions
// WP-17: Shared script library
services.AddSingleton<SharedScriptLibrary>();
// Site-local repository implementations backed by SQLite
services.AddScoped<IExternalSystemRepository, SiteExternalSystemRepository>();
services.AddScoped<INotificationRepository, SiteNotificationRepository>();
return services;
}

View File

@@ -16,12 +16,18 @@ public class ArtifactDeploymentServiceTests
{
private readonly ISiteRepository _siteRepo;
private readonly IDeploymentManagerRepository _deploymentRepo;
private readonly ITemplateEngineRepository _templateRepo;
private readonly IExternalSystemRepository _externalSystemRepo;
private readonly INotificationRepository _notificationRepo;
private readonly IAuditService _audit;
public ArtifactDeploymentServiceTests()
{
_siteRepo = Substitute.For<ISiteRepository>();
_deploymentRepo = Substitute.For<IDeploymentManagerRepository>();
_templateRepo = Substitute.For<ITemplateEngineRepository>();
_externalSystemRepo = Substitute.For<IExternalSystemRepository>();
_notificationRepo = Substitute.For<INotificationRepository>();
_audit = Substitute.For<IAuditService>();
}
@@ -72,7 +78,8 @@ public class ArtifactDeploymentServiceTests
NullLogger<CommunicationService>.Instance);
return new ArtifactDeploymentService(
_siteRepo, _deploymentRepo, comms, _audit,
_siteRepo, _deploymentRepo, _templateRepo, _externalSystemRepo, _notificationRepo,
comms, _audit,
Options.Create(new DeploymentManagerOptions()),
NullLogger<ArtifactDeploymentService>.Instance);
}