refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Site and data connection management.
|
||||
/// - Site CRUD (name, identifier, description)
|
||||
/// - Data connection CRUD (name, protocol, config) — each connection belongs to exactly one site
|
||||
/// - Audit logging
|
||||
/// </summary>
|
||||
public class SiteService
|
||||
{
|
||||
private readonly ISiteRepository _repository;
|
||||
private readonly IAuditService _auditService;
|
||||
|
||||
/// <summary>Initializes the service with its repository and audit collaborators.</summary>
|
||||
/// <param name="repository">Repository for site and data connection persistence.</param>
|
||||
/// <param name="auditService">Audit service for logging CRUD operations.</param>
|
||||
public SiteService(ISiteRepository repository, IAuditService auditService)
|
||||
{
|
||||
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
|
||||
_auditService = auditService ?? throw new ArgumentNullException(nameof(auditService));
|
||||
}
|
||||
|
||||
// --- Site CRUD ---
|
||||
|
||||
/// <summary>Creates a new site with the given name and identifier, rejecting duplicates.</summary>
|
||||
/// <param name="name">Display name of the site.</param>
|
||||
/// <param name="siteIdentifier">Unique machine-readable site identifier.</param>
|
||||
/// <param name="description">Optional description.</param>
|
||||
/// <param name="user">Authenticated username for audit logging.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A successful result containing the new <see cref="Site"/>, or a failure result with a message.</returns>
|
||||
public async Task<Result<Site>> CreateSiteAsync(
|
||||
string name, string siteIdentifier, string? description, string user,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return Result<Site>.Failure("Site name is required.");
|
||||
if (string.IsNullOrWhiteSpace(siteIdentifier))
|
||||
return Result<Site>.Failure("Site identifier is required.");
|
||||
|
||||
var existing = await _repository.GetSiteByIdentifierAsync(siteIdentifier, cancellationToken);
|
||||
if (existing != null)
|
||||
return Result<Site>.Failure($"Site with identifier '{siteIdentifier}' already exists.");
|
||||
|
||||
var site = new Site(name, siteIdentifier) { Description = description };
|
||||
await _repository.AddSiteAsync(site, cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
await _auditService.LogAsync(user, "Create", "Site", site.Id.ToString(), name, site, cancellationToken);
|
||||
|
||||
return Result<Site>.Success(site);
|
||||
}
|
||||
|
||||
/// <summary>Updates the name and description of an existing site.</summary>
|
||||
/// <param name="siteId">Primary key of the site to update.</param>
|
||||
/// <param name="name">New display name.</param>
|
||||
/// <param name="description">New optional description.</param>
|
||||
/// <param name="user">Authenticated username for audit logging.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A successful result containing the updated <see cref="Site"/>, or a failure result if not found.</returns>
|
||||
public async Task<Result<Site>> UpdateSiteAsync(
|
||||
int siteId, string name, string? description, string user,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var site = await _repository.GetSiteByIdAsync(siteId, cancellationToken);
|
||||
if (site == null)
|
||||
return Result<Site>.Failure($"Site with ID {siteId} not found.");
|
||||
|
||||
site.Name = name;
|
||||
site.Description = description;
|
||||
await _repository.UpdateSiteAsync(site, cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
await _auditService.LogAsync(user, "Update", "Site", site.Id.ToString(), name, site, cancellationToken);
|
||||
|
||||
return Result<Site>.Success(site);
|
||||
}
|
||||
|
||||
/// <summary>Deletes a site, rejecting the request if any instances are assigned to it.</summary>
|
||||
/// <param name="siteId">Primary key of the site to delete.</param>
|
||||
/// <param name="user">Authenticated username for audit logging.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A successful result with <c>true</c>, or a failure result with a message if blocked.</returns>
|
||||
public async Task<Result<bool>> DeleteSiteAsync(int siteId, string user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var site = await _repository.GetSiteByIdAsync(siteId, cancellationToken);
|
||||
if (site == null)
|
||||
return Result<bool>.Failure($"Site with ID {siteId} not found.");
|
||||
|
||||
// Check for instances assigned to this site
|
||||
var instances = await _repository.GetInstancesBySiteIdAsync(siteId, cancellationToken);
|
||||
if (instances.Count > 0)
|
||||
{
|
||||
var names = string.Join(", ", instances.Select(i => i.UniqueName).Take(5));
|
||||
return Result<bool>.Failure(
|
||||
$"Cannot delete site '{site.Name}': {instances.Count} instance(s) are assigned to it ({names}{(instances.Count > 5 ? "..." : "")}).");
|
||||
}
|
||||
|
||||
await _repository.DeleteSiteAsync(siteId, cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
await _auditService.LogAsync(user, "Delete", "Site", siteId.ToString(), site.Name, null, cancellationToken);
|
||||
|
||||
return Result<bool>.Success(true);
|
||||
}
|
||||
|
||||
/// <summary>Returns the site with the given primary key, or <c>null</c> if not found.</summary>
|
||||
/// <param name="siteId">Primary key of the site.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public async Task<Site?> GetSiteByIdAsync(int siteId, CancellationToken cancellationToken = default) =>
|
||||
await _repository.GetSiteByIdAsync(siteId, cancellationToken);
|
||||
|
||||
/// <summary>Returns all sites.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
public async Task<IReadOnlyList<Site>> GetAllSitesAsync(CancellationToken cancellationToken = default) =>
|
||||
await _repository.GetAllSitesAsync(cancellationToken);
|
||||
|
||||
// --- Data Connection CRUD ---
|
||||
|
||||
/// <summary>Creates a new data connection owned by the specified site.</summary>
|
||||
/// <param name="siteId">Primary key of the owning site.</param>
|
||||
/// <param name="name">Display name of the data connection.</param>
|
||||
/// <param name="protocol">Protocol identifier (e.g., "OpcUa").</param>
|
||||
/// <param name="primaryConfiguration">JSON configuration for the primary endpoint; may be null.</param>
|
||||
/// <param name="backupConfiguration">JSON configuration for the backup endpoint; may be null.</param>
|
||||
/// <param name="failoverRetryCount">Number of retries before switching to the backup endpoint.</param>
|
||||
/// <param name="user">Authenticated username for audit logging.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A successful result containing the new <see cref="DataConnection"/>, or a failure result.</returns>
|
||||
public async Task<Result<DataConnection>> CreateDataConnectionAsync(
|
||||
int siteId, string name, string protocol, string? primaryConfiguration,
|
||||
string? backupConfiguration, int failoverRetryCount, string user,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return Result<DataConnection>.Failure("Data connection name is required.");
|
||||
if (string.IsNullOrWhiteSpace(protocol))
|
||||
return Result<DataConnection>.Failure("Protocol is required.");
|
||||
|
||||
var connection = new DataConnection(name, protocol, siteId)
|
||||
{
|
||||
PrimaryConfiguration = primaryConfiguration,
|
||||
BackupConfiguration = backupConfiguration,
|
||||
FailoverRetryCount = failoverRetryCount
|
||||
};
|
||||
await _repository.AddDataConnectionAsync(connection, cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
await _auditService.LogAsync(user, "Create", "DataConnection",
|
||||
connection.Id.ToString(), name, connection, cancellationToken);
|
||||
|
||||
return Result<DataConnection>.Success(connection);
|
||||
}
|
||||
|
||||
/// <summary>Updates an existing data connection's configuration.</summary>
|
||||
/// <param name="connectionId">Primary key of the data connection to update.</param>
|
||||
/// <param name="name">New display name.</param>
|
||||
/// <param name="protocol">New protocol identifier.</param>
|
||||
/// <param name="primaryConfiguration">New primary endpoint JSON configuration; may be null.</param>
|
||||
/// <param name="backupConfiguration">New backup endpoint JSON configuration; may be null.</param>
|
||||
/// <param name="failoverRetryCount">New failover retry count.</param>
|
||||
/// <param name="user">Authenticated username for audit logging.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A successful result containing the updated <see cref="DataConnection"/>, or a failure result if not found.</returns>
|
||||
public async Task<Result<DataConnection>> UpdateDataConnectionAsync(
|
||||
int connectionId, string name, string protocol, string? primaryConfiguration,
|
||||
string? backupConfiguration, int failoverRetryCount, string user,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var connection = await _repository.GetDataConnectionByIdAsync(connectionId, cancellationToken);
|
||||
if (connection == null)
|
||||
return Result<DataConnection>.Failure($"Data connection with ID {connectionId} not found.");
|
||||
|
||||
connection.Name = name;
|
||||
connection.Protocol = protocol;
|
||||
connection.PrimaryConfiguration = primaryConfiguration;
|
||||
connection.BackupConfiguration = backupConfiguration;
|
||||
connection.FailoverRetryCount = failoverRetryCount;
|
||||
await _repository.UpdateDataConnectionAsync(connection, cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
await _auditService.LogAsync(user, "Update", "DataConnection",
|
||||
connection.Id.ToString(), name, connection, cancellationToken);
|
||||
|
||||
return Result<DataConnection>.Success(connection);
|
||||
}
|
||||
|
||||
/// <summary>Deletes a data connection by its primary key.</summary>
|
||||
/// <param name="connectionId">Primary key of the data connection to delete.</param>
|
||||
/// <param name="user">Authenticated username for audit logging.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A successful result with <c>true</c>, or a failure result if not found.</returns>
|
||||
public async Task<Result<bool>> DeleteDataConnectionAsync(
|
||||
int connectionId, string user, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var connection = await _repository.GetDataConnectionByIdAsync(connectionId, cancellationToken);
|
||||
if (connection == null)
|
||||
return Result<bool>.Failure($"Data connection with ID {connectionId} not found.");
|
||||
|
||||
await _repository.DeleteDataConnectionAsync(connectionId, cancellationToken);
|
||||
await _repository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
await _auditService.LogAsync(user, "Delete", "DataConnection",
|
||||
connectionId.ToString(), connection.Name, null, cancellationToken);
|
||||
|
||||
return Result<bool>.Success(true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user