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

@@ -8,9 +8,7 @@ namespace ScadaLink.TemplateEngine.Services;
/// <summary>
/// Site and data connection management.
/// - Site CRUD (name, identifier, description)
/// - Data connection CRUD (name, protocol, config)
/// - Assign connections to sites
/// - Connection names not standardized across sites
/// - Data connection CRUD (name, protocol, config) — each connection belongs to exactly one site
/// - Audit logging
/// </summary>
public class SiteService
@@ -98,7 +96,7 @@ public class SiteService
// --- Data Connection CRUD ---
public async Task<Result<DataConnection>> CreateDataConnectionAsync(
string name, string protocol, string? configuration, string user,
int siteId, string name, string protocol, string? configuration, string user,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(name))
@@ -106,7 +104,7 @@ public class SiteService
if (string.IsNullOrWhiteSpace(protocol))
return Result<DataConnection>.Failure("Protocol is required.");
var connection = new DataConnection(name, protocol) { Configuration = configuration };
var connection = new DataConnection(name, protocol, siteId) { Configuration = configuration };
await _repository.AddDataConnectionAsync(connection, cancellationToken);
await _repository.SaveChangesAsync(cancellationToken);
@@ -151,56 +149,4 @@ public class SiteService
return Result<bool>.Success(true);
}
// --- Site-Connection Assignment ---
public async Task<Result<SiteDataConnectionAssignment>> AssignConnectionToSiteAsync(
int siteId, int dataConnectionId, string user,
CancellationToken cancellationToken = default)
{
var site = await _repository.GetSiteByIdAsync(siteId, cancellationToken);
if (site == null)
return Result<SiteDataConnectionAssignment>.Failure($"Site with ID {siteId} not found.");
var connection = await _repository.GetDataConnectionByIdAsync(dataConnectionId, cancellationToken);
if (connection == null)
return Result<SiteDataConnectionAssignment>.Failure($"Data connection with ID {dataConnectionId} not found.");
// Check if assignment already exists
var existing = await _repository.GetSiteDataConnectionAssignmentAsync(siteId, dataConnectionId, cancellationToken);
if (existing != null)
return Result<SiteDataConnectionAssignment>.Failure(
$"Data connection '{connection.Name}' is already assigned to site '{site.Name}'.");
var assignment = new SiteDataConnectionAssignment
{
SiteId = siteId,
DataConnectionId = dataConnectionId
};
await _repository.AddSiteDataConnectionAssignmentAsync(assignment, cancellationToken);
await _repository.SaveChangesAsync(cancellationToken);
await _auditService.LogAsync(user, "AssignConnection", "SiteDataConnectionAssignment",
assignment.Id.ToString(), $"{site.Name}/{connection.Name}", assignment, cancellationToken);
return Result<SiteDataConnectionAssignment>.Success(assignment);
}
public async Task<Result<bool>> RemoveConnectionFromSiteAsync(
int siteId, int dataConnectionId, string user,
CancellationToken cancellationToken = default)
{
var assignment = await _repository.GetSiteDataConnectionAssignmentAsync(siteId, dataConnectionId, cancellationToken);
if (assignment == null)
return Result<bool>.Failure("Assignment not found.");
await _repository.DeleteSiteDataConnectionAssignmentAsync(assignment.Id, cancellationToken);
await _repository.SaveChangesAsync(cancellationToken);
await _auditService.LogAsync(user, "RemoveConnection", "SiteDataConnectionAssignment",
assignment.Id.ToString(), $"Site:{siteId}/Conn:{dataConnectionId}", null, cancellationToken);
return Result<bool>.Success(true);
}
}