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:
@@ -12,8 +12,8 @@ namespace ScadaLink.DeploymentManager;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// WP-7: System-wide artifact deployment.
|
/// WP-7: System-wide artifact deployment.
|
||||||
/// Broadcasts artifacts (shared scripts, external systems, notification lists, DB connections)
|
/// Broadcasts artifacts (shared scripts, external systems, notification lists, DB connections,
|
||||||
/// to all sites with per-site tracking.
|
/// data connections, and SMTP configurations) to all sites with per-site tracking.
|
||||||
///
|
///
|
||||||
/// - Successful sites are NOT rolled back on other failures.
|
/// - Successful sites are NOT rolled back on other failures.
|
||||||
/// - Failed sites are retryable individually.
|
/// - Failed sites are retryable individually.
|
||||||
@@ -24,6 +24,9 @@ public class ArtifactDeploymentService
|
|||||||
{
|
{
|
||||||
private readonly ISiteRepository _siteRepo;
|
private readonly ISiteRepository _siteRepo;
|
||||||
private readonly IDeploymentManagerRepository _deploymentRepo;
|
private readonly IDeploymentManagerRepository _deploymentRepo;
|
||||||
|
private readonly ITemplateEngineRepository _templateRepo;
|
||||||
|
private readonly IExternalSystemRepository _externalSystemRepo;
|
||||||
|
private readonly INotificationRepository _notificationRepo;
|
||||||
private readonly CommunicationService _communicationService;
|
private readonly CommunicationService _communicationService;
|
||||||
private readonly IAuditService _auditService;
|
private readonly IAuditService _auditService;
|
||||||
private readonly DeploymentManagerOptions _options;
|
private readonly DeploymentManagerOptions _options;
|
||||||
@@ -32,6 +35,9 @@ public class ArtifactDeploymentService
|
|||||||
public ArtifactDeploymentService(
|
public ArtifactDeploymentService(
|
||||||
ISiteRepository siteRepo,
|
ISiteRepository siteRepo,
|
||||||
IDeploymentManagerRepository deploymentRepo,
|
IDeploymentManagerRepository deploymentRepo,
|
||||||
|
ITemplateEngineRepository templateRepo,
|
||||||
|
IExternalSystemRepository externalSystemRepo,
|
||||||
|
INotificationRepository notificationRepo,
|
||||||
CommunicationService communicationService,
|
CommunicationService communicationService,
|
||||||
IAuditService auditService,
|
IAuditService auditService,
|
||||||
IOptions<DeploymentManagerOptions> options,
|
IOptions<DeploymentManagerOptions> options,
|
||||||
@@ -39,12 +45,80 @@ public class ArtifactDeploymentService
|
|||||||
{
|
{
|
||||||
_siteRepo = siteRepo;
|
_siteRepo = siteRepo;
|
||||||
_deploymentRepo = deploymentRepo;
|
_deploymentRepo = deploymentRepo;
|
||||||
|
_templateRepo = templateRepo;
|
||||||
|
_externalSystemRepo = externalSystemRepo;
|
||||||
|
_notificationRepo = notificationRepo;
|
||||||
_communicationService = communicationService;
|
_communicationService = communicationService;
|
||||||
_auditService = auditService;
|
_auditService = auditService;
|
||||||
_options = options.Value;
|
_options = options.Value;
|
||||||
_logger = logger;
|
_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>
|
/// <summary>
|
||||||
/// Deploys artifacts to all sites. Returns per-site result matrix.
|
/// Deploys artifacts to all sites. Returns per-site result matrix.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -152,14 +152,8 @@ try
|
|||||||
services.AddExternalSystemGateway();
|
services.AddExternalSystemGateway();
|
||||||
services.AddNotificationService();
|
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
|
// 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";
|
var siteDbPath = context.Configuration["ScadaLink:Database:SiteDbPath"] ?? "site.db";
|
||||||
services.AddSiteRuntime($"Data Source={siteDbPath}");
|
services.AddSiteRuntime($"Data Source={siteDbPath}");
|
||||||
services.AddDataConnectionLayer();
|
services.AddDataConnectionLayer();
|
||||||
|
|||||||
@@ -18,8 +18,7 @@
|
|||||||
"MinNrOfMembers": 1
|
"MinNrOfMembers": 1
|
||||||
},
|
},
|
||||||
"Database": {
|
"Database": {
|
||||||
"SiteDbPath": "./data/scadalink.db",
|
"SiteDbPath": "./data/scadalink.db"
|
||||||
"ConfigurationDb": "Server=localhost,1433;Database=ScadaLinkConfig;User Id=scadalink_app;Password=ScadaLink_Dev1#;TrustServerCertificate=true"
|
|
||||||
},
|
},
|
||||||
"DataConnection": {
|
"DataConnection": {
|
||||||
"ReconnectInterval": "00:00:05",
|
"ReconnectInterval": "00:00:05",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using ScadaLink.Commons.Interfaces.Repositories;
|
||||||
using ScadaLink.SiteRuntime.Persistence;
|
using ScadaLink.SiteRuntime.Persistence;
|
||||||
|
using ScadaLink.SiteRuntime.Repositories;
|
||||||
using ScadaLink.SiteRuntime.Scripts;
|
using ScadaLink.SiteRuntime.Scripts;
|
||||||
|
|
||||||
namespace ScadaLink.SiteRuntime;
|
namespace ScadaLink.SiteRuntime;
|
||||||
@@ -38,6 +40,10 @@ public static class ServiceCollectionExtensions
|
|||||||
// WP-17: Shared script library
|
// WP-17: Shared script library
|
||||||
services.AddSingleton<SharedScriptLibrary>();
|
services.AddSingleton<SharedScriptLibrary>();
|
||||||
|
|
||||||
|
// Site-local repository implementations backed by SQLite
|
||||||
|
services.AddScoped<IExternalSystemRepository, SiteExternalSystemRepository>();
|
||||||
|
services.AddScoped<INotificationRepository, SiteNotificationRepository>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,18 @@ public class ArtifactDeploymentServiceTests
|
|||||||
{
|
{
|
||||||
private readonly ISiteRepository _siteRepo;
|
private readonly ISiteRepository _siteRepo;
|
||||||
private readonly IDeploymentManagerRepository _deploymentRepo;
|
private readonly IDeploymentManagerRepository _deploymentRepo;
|
||||||
|
private readonly ITemplateEngineRepository _templateRepo;
|
||||||
|
private readonly IExternalSystemRepository _externalSystemRepo;
|
||||||
|
private readonly INotificationRepository _notificationRepo;
|
||||||
private readonly IAuditService _audit;
|
private readonly IAuditService _audit;
|
||||||
|
|
||||||
public ArtifactDeploymentServiceTests()
|
public ArtifactDeploymentServiceTests()
|
||||||
{
|
{
|
||||||
_siteRepo = Substitute.For<ISiteRepository>();
|
_siteRepo = Substitute.For<ISiteRepository>();
|
||||||
_deploymentRepo = Substitute.For<IDeploymentManagerRepository>();
|
_deploymentRepo = Substitute.For<IDeploymentManagerRepository>();
|
||||||
|
_templateRepo = Substitute.For<ITemplateEngineRepository>();
|
||||||
|
_externalSystemRepo = Substitute.For<IExternalSystemRepository>();
|
||||||
|
_notificationRepo = Substitute.For<INotificationRepository>();
|
||||||
_audit = Substitute.For<IAuditService>();
|
_audit = Substitute.For<IAuditService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +78,8 @@ public class ArtifactDeploymentServiceTests
|
|||||||
NullLogger<CommunicationService>.Instance);
|
NullLogger<CommunicationService>.Instance);
|
||||||
|
|
||||||
return new ArtifactDeploymentService(
|
return new ArtifactDeploymentService(
|
||||||
_siteRepo, _deploymentRepo, comms, _audit,
|
_siteRepo, _deploymentRepo, _templateRepo, _externalSystemRepo, _notificationRepo,
|
||||||
|
comms, _audit,
|
||||||
Options.Create(new DeploymentManagerOptions()),
|
Options.Create(new DeploymentManagerOptions()),
|
||||||
NullLogger<ArtifactDeploymentService>.Instance);
|
NullLogger<ArtifactDeploymentService>.Instance);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user