refactor: simplify data connections from many-to-many site assignment to direct site ownership

Replace SiteDataConnectionAssignment join table with a direct SiteId FK on DataConnection,
simplifying the data model, repositories, UI, CLI, and deployment service.
This commit is contained in:
Joseph Doherty
2026-03-21 21:07:10 -04:00
parent cd6efeea90
commit 970d0a5cb3
25 changed files with 1543 additions and 490 deletions

View File

@@ -55,16 +55,18 @@ public class ArtifactDeploymentService
}
/// <summary>
/// Collects all artifact types from repositories and builds a <see cref="DeployArtifactsCommand"/>.
/// Collects all artifact types from repositories and builds a <see cref="DeployArtifactsCommand"/>
/// scoped to a specific site's data connections.
/// </summary>
public async Task<DeployArtifactsCommand> BuildDeployArtifactsCommandAsync(
int siteId,
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 dataConnections = await _siteRepo.GetDataConnectionsBySiteIdAsync(siteId, cancellationToken);
var smtpConfigurations = await _notificationRepo.GetAllSmtpConfigurationsAsync(cancellationToken);
// Map shared scripts
@@ -120,10 +122,10 @@ public class ArtifactDeploymentService
}
/// <summary>
/// Deploys artifacts to all sites. Returns per-site result matrix.
/// Deploys artifacts to all sites. Builds a per-site command with that site's data connections.
/// Returns per-site result matrix.
/// </summary>
public async Task<Result<ArtifactDeploymentSummary>> DeployToAllSitesAsync(
DeployArtifactsCommand command,
string user,
CancellationToken cancellationToken = default)
{
@@ -131,9 +133,17 @@ public class ArtifactDeploymentService
if (sites.Count == 0)
return Result<ArtifactDeploymentSummary>.Failure("No sites configured.");
var deploymentId = Guid.NewGuid().ToString("N");
var perSiteResults = new Dictionary<string, SiteArtifactResult>();
// Deploy to each site with per-site timeout
// Build per-site commands sequentially (DbContext is not thread-safe)
var siteCommands = new Dictionary<int, DeployArtifactsCommand>();
foreach (var site in sites)
{
siteCommands[site.Id] = await BuildDeployArtifactsCommandAsync(site.Id, cancellationToken);
}
// Deploy to each site in parallel with per-site timeout
var tasks = sites.Select(async site =>
{
try
@@ -141,6 +151,8 @@ public class ArtifactDeploymentService
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(_options.ArtifactDeploymentTimeoutPerSite);
var command = siteCommands[site.Id];
_logger.LogInformation(
"Deploying artifacts to site {SiteId} ({SiteName}), deploymentId={DeploymentId}",
site.SiteIdentifier, site.Name, command.DeploymentId);
@@ -188,13 +200,13 @@ public class ArtifactDeploymentService
await _deploymentRepo.SaveChangesAsync(cancellationToken);
var summary = new ArtifactDeploymentSummary(
command.DeploymentId,
deploymentId,
results.ToList(),
results.Count(r => r.Success),
results.Count(r => !r.Success));
await _auditService.LogAsync(user, "DeployArtifacts", "SystemArtifact",
command.DeploymentId, "Artifacts",
deploymentId, "Artifacts",
new { summary.SuccessCount, summary.FailureCount },
cancellationToken);
@@ -205,8 +217,8 @@ public class ArtifactDeploymentService
/// WP-7: Retry artifact deployment to a specific site that previously failed.
/// </summary>
public async Task<Result<SiteArtifactResult>> RetryForSiteAsync(
string siteId,
DeployArtifactsCommand command,
int siteDbId,
string siteIdentifier,
string user,
CancellationToken cancellationToken = default)
{
@@ -215,12 +227,13 @@ public class ArtifactDeploymentService
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(_options.ArtifactDeploymentTimeoutPerSite);
var response = await _communicationService.DeployArtifactsAsync(siteId, command, cts.Token);
var command = await BuildDeployArtifactsCommandAsync(siteDbId, cts.Token);
var response = await _communicationService.DeployArtifactsAsync(siteIdentifier, command, cts.Token);
var result = new SiteArtifactResult(siteId, siteId, response.Success, response.ErrorMessage);
var result = new SiteArtifactResult(siteIdentifier, siteIdentifier, response.Success, response.ErrorMessage);
await _auditService.LogAsync(user, "RetryArtifactDeployment", "SystemArtifact",
command.DeploymentId, siteId, new { response.Success }, cancellationToken);
command.DeploymentId, siteIdentifier, new { response.Success }, cancellationToken);
return response.Success
? Result<SiteArtifactResult>.Success(result)
@@ -228,7 +241,7 @@ public class ArtifactDeploymentService
}
catch (Exception ex)
{
return Result<SiteArtifactResult>.Failure($"Retry failed for site {siteId}: {ex.Message}");
return Result<SiteArtifactResult>.Failure($"Retry failed for site {siteIdentifier}: {ex.Message}");
}
}
}