Phase 0 WP-0.2–0.9: Implement Commons (types, entities, interfaces, messages, protocol, tests)

- WP-0.2: Namespace/folder skeleton (26 directories)
- WP-0.3: Shared data types (6 enums, RetryPolicy, Result<T>)
- WP-0.4: 24 domain entity POCOs across 10 domain areas
- WP-0.5: 7 repository interfaces with full CRUD signatures
- WP-0.6: IAuditService cross-cutting interface
- WP-0.7: 26 message contract records across 8 concern areas
- WP-0.8: IDataConnection protocol abstraction with batch ops
- WP-0.9: 8 architectural constraint enforcement tests
All 40 tests pass, zero warnings.
This commit is contained in:
Joseph Doherty
2026-03-16 18:48:24 -04:00
parent fed5f5a82c
commit 22e1eba58a
78 changed files with 1530 additions and 16 deletions

View File

@@ -1,6 +0,0 @@
namespace ScadaLink.Commons;
public class Class1
{
}

View File

@@ -0,0 +1,22 @@
namespace ScadaLink.Commons.Entities.Audit;
public class AuditLogEntry
{
public int Id { get; set; }
public string User { get; set; }
public string Action { get; set; }
public string EntityType { get; set; }
public string EntityId { get; set; }
public string EntityName { get; set; }
public string? AfterStateJson { get; set; }
public DateTimeOffset Timestamp { get; set; }
public AuditLogEntry(string user, string action, string entityType, string entityId, string entityName)
{
User = user ?? throw new ArgumentNullException(nameof(user));
Action = action ?? throw new ArgumentNullException(nameof(action));
EntityType = entityType ?? throw new ArgumentNullException(nameof(entityType));
EntityId = entityId ?? throw new ArgumentNullException(nameof(entityId));
EntityName = entityName ?? throw new ArgumentNullException(nameof(entityName));
}
}

View File

@@ -0,0 +1,21 @@
using ScadaLink.Commons.Types.Enums;
namespace ScadaLink.Commons.Entities.Deployment;
public class DeploymentRecord
{
public int Id { get; set; }
public int InstanceId { get; set; }
public DeploymentStatus Status { get; set; }
public string DeploymentId { get; set; }
public string? RevisionHash { get; set; }
public string DeployedBy { get; set; }
public DateTimeOffset DeployedAt { get; set; }
public DateTimeOffset? CompletedAt { get; set; }
public DeploymentRecord(string deploymentId, string deployedBy)
{
DeploymentId = deploymentId ?? throw new ArgumentNullException(nameof(deploymentId));
DeployedBy = deployedBy ?? throw new ArgumentNullException(nameof(deployedBy));
}
}

View File

@@ -0,0 +1,16 @@
namespace ScadaLink.Commons.Entities.Deployment;
public class SystemArtifactDeploymentRecord
{
public int Id { get; set; }
public string ArtifactType { get; set; }
public string DeployedBy { get; set; }
public DateTimeOffset DeployedAt { get; set; }
public string? PerSiteStatus { get; set; }
public SystemArtifactDeploymentRecord(string artifactType, string deployedBy)
{
ArtifactType = artifactType ?? throw new ArgumentNullException(nameof(artifactType));
DeployedBy = deployedBy ?? throw new ArgumentNullException(nameof(deployedBy));
}
}

View File

@@ -0,0 +1,16 @@
namespace ScadaLink.Commons.Entities.ExternalSystems;
public class DatabaseConnectionDefinition
{
public int Id { get; set; }
public string Name { get; set; }
public string ConnectionString { get; set; }
public int MaxRetries { get; set; }
public TimeSpan RetryDelay { get; set; }
public DatabaseConnectionDefinition(string name, string connectionString)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
}
}

View File

@@ -0,0 +1,19 @@
namespace ScadaLink.Commons.Entities.ExternalSystems;
public class ExternalSystemDefinition
{
public int Id { get; set; }
public string Name { get; set; }
public string EndpointUrl { get; set; }
public string AuthType { get; set; }
public string? AuthConfiguration { get; set; }
public int MaxRetries { get; set; }
public TimeSpan RetryDelay { get; set; }
public ExternalSystemDefinition(string name, string endpointUrl, string authType)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
EndpointUrl = endpointUrl ?? throw new ArgumentNullException(nameof(endpointUrl));
AuthType = authType ?? throw new ArgumentNullException(nameof(authType));
}
}

View File

@@ -0,0 +1,19 @@
namespace ScadaLink.Commons.Entities.ExternalSystems;
public class ExternalSystemMethod
{
public int Id { get; set; }
public int ExternalSystemDefinitionId { get; set; }
public string Name { get; set; }
public string HttpMethod { get; set; }
public string Path { get; set; }
public string? ParameterDefinitions { get; set; }
public string? ReturnDefinition { get; set; }
public ExternalSystemMethod(string name, string httpMethod, string path)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
HttpMethod = httpMethod ?? throw new ArgumentNullException(nameof(httpMethod));
Path = path ?? throw new ArgumentNullException(nameof(path));
}
}

View File

@@ -0,0 +1,15 @@
namespace ScadaLink.Commons.Entities.InboundApi;
public class ApiKey
{
public int Id { get; set; }
public string Name { get; set; }
public string KeyValue { get; set; }
public bool IsEnabled { get; set; }
public ApiKey(string name, string keyValue)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
KeyValue = keyValue ?? throw new ArgumentNullException(nameof(keyValue));
}
}

View File

@@ -0,0 +1,18 @@
namespace ScadaLink.Commons.Entities.InboundApi;
public class ApiMethod
{
public int Id { get; set; }
public string Name { get; set; }
public string Script { get; set; }
public string? ApprovedApiKeyIds { get; set; }
public string? ParameterDefinitions { get; set; }
public string? ReturnDefinition { get; set; }
public int TimeoutSeconds { get; set; }
public ApiMethod(string name, string script)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Script = script ?? throw new ArgumentNullException(nameof(script));
}
}

View File

@@ -0,0 +1,15 @@
namespace ScadaLink.Commons.Entities.Instances;
public class Area
{
public int Id { get; set; }
public int SiteId { get; set; }
public string Name { get; set; }
public int? ParentAreaId { get; set; }
public ICollection<Area> Children { get; set; } = new List<Area>();
public Area(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}

View File

@@ -0,0 +1,20 @@
using ScadaLink.Commons.Types.Enums;
namespace ScadaLink.Commons.Entities.Instances;
public class Instance
{
public int Id { get; set; }
public int TemplateId { get; set; }
public int SiteId { get; set; }
public int? AreaId { get; set; }
public string UniqueName { get; set; }
public InstanceState State { get; set; }
public ICollection<InstanceAttributeOverride> AttributeOverrides { get; set; } = new List<InstanceAttributeOverride>();
public ICollection<InstanceConnectionBinding> ConnectionBindings { get; set; } = new List<InstanceConnectionBinding>();
public Instance(string uniqueName)
{
UniqueName = uniqueName ?? throw new ArgumentNullException(nameof(uniqueName));
}
}

View File

@@ -0,0 +1,14 @@
namespace ScadaLink.Commons.Entities.Instances;
public class InstanceAttributeOverride
{
public int Id { get; set; }
public int InstanceId { get; set; }
public string AttributeName { get; set; }
public string? OverrideValue { get; set; }
public InstanceAttributeOverride(string attributeName)
{
AttributeName = attributeName ?? throw new ArgumentNullException(nameof(attributeName));
}
}

View File

@@ -0,0 +1,14 @@
namespace ScadaLink.Commons.Entities.Instances;
public class InstanceConnectionBinding
{
public int Id { get; set; }
public int InstanceId { get; set; }
public string AttributeName { get; set; }
public int DataConnectionId { get; set; }
public InstanceConnectionBinding(string attributeName)
{
AttributeName = attributeName ?? throw new ArgumentNullException(nameof(attributeName));
}
}

View File

@@ -0,0 +1,13 @@
namespace ScadaLink.Commons.Entities.Notifications;
public class NotificationList
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<NotificationRecipient> Recipients { get; set; } = new List<NotificationRecipient>();
public NotificationList(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}

View File

@@ -0,0 +1,15 @@
namespace ScadaLink.Commons.Entities.Notifications;
public class NotificationRecipient
{
public int Id { get; set; }
public int NotificationListId { get; set; }
public string Name { get; set; }
public string EmailAddress { get; set; }
public NotificationRecipient(string name, string emailAddress)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
EmailAddress = emailAddress ?? throw new ArgumentNullException(nameof(emailAddress));
}
}

View File

@@ -0,0 +1,23 @@
namespace ScadaLink.Commons.Entities.Notifications;
public class SmtpConfiguration
{
public int Id { get; set; }
public string Host { get; set; }
public int Port { get; set; }
public string AuthType { get; set; }
public string? Credentials { get; set; }
public string? TlsMode { get; set; }
public string FromAddress { get; set; }
public int ConnectionTimeoutSeconds { get; set; }
public int MaxConcurrentConnections { get; set; }
public int MaxRetries { get; set; }
public TimeSpan RetryDelay { get; set; }
public SmtpConfiguration(string host, string authType, string fromAddress)
{
Host = host ?? throw new ArgumentNullException(nameof(host));
AuthType = authType ?? throw new ArgumentNullException(nameof(authType));
FromAddress = fromAddress ?? throw new ArgumentNullException(nameof(fromAddress));
}
}

View File

@@ -0,0 +1,16 @@
namespace ScadaLink.Commons.Entities.Scripts;
public class SharedScript
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public string? ParameterDefinitions { get; set; }
public string? ReturnDefinition { get; set; }
public SharedScript(string name, string code)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Code = code ?? throw new ArgumentNullException(nameof(code));
}
}

View File

@@ -0,0 +1,14 @@
namespace ScadaLink.Commons.Entities.Security;
public class LdapGroupMapping
{
public int Id { get; set; }
public string LdapGroupName { get; set; }
public string Role { get; set; }
public LdapGroupMapping(string ldapGroupName, string role)
{
LdapGroupName = ldapGroupName ?? throw new ArgumentNullException(nameof(ldapGroupName));
Role = role ?? throw new ArgumentNullException(nameof(role));
}
}

View File

@@ -0,0 +1,8 @@
namespace ScadaLink.Commons.Entities.Security;
public class SiteScopeRule
{
public int Id { get; set; }
public int LdapGroupMappingId { get; set; }
public int SiteId { get; set; }
}

View File

@@ -0,0 +1,15 @@
namespace ScadaLink.Commons.Entities.Sites;
public class DataConnection
{
public int Id { get; set; }
public string Name { get; set; }
public string Protocol { get; set; }
public string? Configuration { get; set; }
public DataConnection(string name, string protocol)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Protocol = protocol ?? throw new ArgumentNullException(nameof(protocol));
}
}

View File

@@ -0,0 +1,15 @@
namespace ScadaLink.Commons.Entities.Sites;
public class Site
{
public int Id { get; set; }
public string Name { get; set; }
public string SiteIdentifier { get; set; }
public string? Description { get; set; }
public Site(string name, string siteIdentifier)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
SiteIdentifier = siteIdentifier ?? throw new ArgumentNullException(nameof(siteIdentifier));
}
}

View File

@@ -0,0 +1,8 @@
namespace ScadaLink.Commons.Entities.Sites;
public class SiteDataConnectionAssignment
{
public int Id { get; set; }
public int SiteId { get; set; }
public int DataConnectionId { get; set; }
}

View File

@@ -0,0 +1,18 @@
namespace ScadaLink.Commons.Entities.Templates;
public class Template
{
public int Id { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public int? ParentTemplateId { get; set; }
public ICollection<TemplateAttribute> Attributes { get; set; } = new List<TemplateAttribute>();
public ICollection<TemplateAlarm> Alarms { get; set; } = new List<TemplateAlarm>();
public ICollection<TemplateScript> Scripts { get; set; } = new List<TemplateScript>();
public ICollection<TemplateComposition> Compositions { get; set; } = new List<TemplateComposition>();
public Template(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}

View File

@@ -0,0 +1,21 @@
using ScadaLink.Commons.Types.Enums;
namespace ScadaLink.Commons.Entities.Templates;
public class TemplateAlarm
{
public int Id { get; set; }
public int TemplateId { get; set; }
public string Name { get; set; }
public string? Description { get; set; }
public int PriorityLevel { get; set; }
public bool IsLocked { get; set; }
public AlarmTriggerType TriggerType { get; set; }
public string? TriggerConfiguration { get; set; }
public int? OnTriggerScriptId { get; set; }
public TemplateAlarm(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}

View File

@@ -0,0 +1,20 @@
using ScadaLink.Commons.Types.Enums;
namespace ScadaLink.Commons.Entities.Templates;
public class TemplateAttribute
{
public int Id { get; set; }
public int TemplateId { get; set; }
public string Name { get; set; }
public string? Value { get; set; }
public DataType DataType { get; set; }
public bool IsLocked { get; set; }
public string? Description { get; set; }
public string? DataSourceReference { get; set; }
public TemplateAttribute(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
}

View File

@@ -0,0 +1,14 @@
namespace ScadaLink.Commons.Entities.Templates;
public class TemplateComposition
{
public int Id { get; set; }
public int TemplateId { get; set; }
public int ComposedTemplateId { get; set; }
public string InstanceName { get; set; }
public TemplateComposition(string instanceName)
{
InstanceName = instanceName ?? throw new ArgumentNullException(nameof(instanceName));
}
}

View File

@@ -0,0 +1,21 @@
namespace ScadaLink.Commons.Entities.Templates;
public class TemplateScript
{
public int Id { get; set; }
public int TemplateId { get; set; }
public string Name { get; set; }
public bool IsLocked { get; set; }
public string Code { get; set; }
public string? TriggerType { get; set; }
public string? TriggerConfiguration { get; set; }
public string? ParameterDefinitions { get; set; }
public string? ReturnDefinition { get; set; }
public TimeSpan? MinTimeBetweenRuns { get; set; }
public TemplateScript(string name, string code)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Code = code ?? throw new ArgumentNullException(nameof(code));
}
}

View File

@@ -0,0 +1,25 @@
using ScadaLink.Commons.Types.Enums;
namespace ScadaLink.Commons.Interfaces.Protocol;
public enum QualityCode { Good, Bad, Uncertain }
public record TagValue(object? Value, QualityCode Quality, DateTimeOffset Timestamp);
public record ReadResult(bool Success, TagValue? Value, string? ErrorMessage);
public record WriteResult(bool Success, string? ErrorMessage);
public delegate void SubscriptionCallback(string tagPath, TagValue value);
public interface IDataConnection : IAsyncDisposable
{
Task ConnectAsync(IDictionary<string, string> connectionDetails, CancellationToken cancellationToken = default);
Task DisconnectAsync(CancellationToken cancellationToken = default);
Task<string> SubscribeAsync(string tagPath, SubscriptionCallback callback, CancellationToken cancellationToken = default);
Task UnsubscribeAsync(string subscriptionId, CancellationToken cancellationToken = default);
Task<ReadResult> ReadAsync(string tagPath, CancellationToken cancellationToken = default);
Task<IReadOnlyDictionary<string, ReadResult>> ReadBatchAsync(IEnumerable<string> tagPaths, CancellationToken cancellationToken = default);
Task<WriteResult> WriteAsync(string tagPath, object? value, CancellationToken cancellationToken = default);
Task<IReadOnlyDictionary<string, WriteResult>> WriteBatchAsync(IDictionary<string, object?> values, CancellationToken cancellationToken = default);
Task<bool> WriteBatchAndWaitAsync(IDictionary<string, object?> values, string flagPath, object? flagValue, string responsePath, object? responseValue, TimeSpan timeout, CancellationToken cancellationToken = default);
ConnectionHealth Status { get; }
}

View File

@@ -0,0 +1,19 @@
using ScadaLink.Commons.Entities.Deployment;
using ScadaLink.Commons.Entities.Instances;
using ScadaLink.Commons.Entities.Sites;
using ScadaLink.Commons.Entities.Templates;
namespace ScadaLink.Commons.Interfaces.Repositories;
public interface ICentralUiRepository
{
Task<IReadOnlyList<Site>> GetAllSitesAsync(CancellationToken cancellationToken = default);
Task<IReadOnlyList<DataConnection>> GetDataConnectionsBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
Task<IReadOnlyList<SiteDataConnectionAssignment>> GetAllSiteDataConnectionAssignmentsAsync(CancellationToken cancellationToken = default);
Task<IReadOnlyList<Template>> GetTemplateTreeAsync(CancellationToken cancellationToken = default);
Task<IReadOnlyList<Instance>> GetInstancesFilteredAsync(int? siteId = null, int? templateId = null, string? searchTerm = null, CancellationToken cancellationToken = default);
Task<IReadOnlyList<DeploymentRecord>> GetRecentDeploymentsAsync(int count, CancellationToken cancellationToken = default);
Task<IReadOnlyList<Area>> GetAreaTreeBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,26 @@
using ScadaLink.Commons.Entities.Deployment;
using ScadaLink.Commons.Types.Enums;
namespace ScadaLink.Commons.Interfaces.Repositories;
public interface IDeploymentManagerRepository
{
// DeploymentRecord
Task<DeploymentRecord?> GetDeploymentRecordByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<DeploymentRecord>> GetAllDeploymentRecordsAsync(CancellationToken cancellationToken = default);
Task<IReadOnlyList<DeploymentRecord>> GetDeploymentsByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
Task<DeploymentRecord?> GetCurrentDeploymentStatusAsync(int instanceId, CancellationToken cancellationToken = default);
Task<DeploymentRecord?> GetDeploymentByDeploymentIdAsync(string deploymentId, CancellationToken cancellationToken = default);
Task AddDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default);
Task UpdateDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default);
Task DeleteDeploymentRecordAsync(int id, CancellationToken cancellationToken = default);
// SystemArtifactDeploymentRecord
Task<SystemArtifactDeploymentRecord?> GetSystemArtifactDeploymentByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<SystemArtifactDeploymentRecord>> GetAllSystemArtifactDeploymentsAsync(CancellationToken cancellationToken = default);
Task AddSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default);
Task UpdateSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default);
Task DeleteSystemArtifactDeploymentAsync(int id, CancellationToken cancellationToken = default);
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,29 @@
using ScadaLink.Commons.Entities.ExternalSystems;
namespace ScadaLink.Commons.Interfaces.Repositories;
public interface IExternalSystemRepository
{
// ExternalSystemDefinition
Task<ExternalSystemDefinition?> GetExternalSystemByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<ExternalSystemDefinition>> GetAllExternalSystemsAsync(CancellationToken cancellationToken = default);
Task AddExternalSystemAsync(ExternalSystemDefinition definition, CancellationToken cancellationToken = default);
Task UpdateExternalSystemAsync(ExternalSystemDefinition definition, CancellationToken cancellationToken = default);
Task DeleteExternalSystemAsync(int id, CancellationToken cancellationToken = default);
// ExternalSystemMethod
Task<ExternalSystemMethod?> GetExternalSystemMethodByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<ExternalSystemMethod>> GetMethodsByExternalSystemIdAsync(int externalSystemId, CancellationToken cancellationToken = default);
Task AddExternalSystemMethodAsync(ExternalSystemMethod method, CancellationToken cancellationToken = default);
Task UpdateExternalSystemMethodAsync(ExternalSystemMethod method, CancellationToken cancellationToken = default);
Task DeleteExternalSystemMethodAsync(int id, CancellationToken cancellationToken = default);
// DatabaseConnectionDefinition
Task<DatabaseConnectionDefinition?> GetDatabaseConnectionByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<DatabaseConnectionDefinition>> GetAllDatabaseConnectionsAsync(CancellationToken cancellationToken = default);
Task AddDatabaseConnectionAsync(DatabaseConnectionDefinition definition, CancellationToken cancellationToken = default);
Task UpdateDatabaseConnectionAsync(DatabaseConnectionDefinition definition, CancellationToken cancellationToken = default);
Task DeleteDatabaseConnectionAsync(int id, CancellationToken cancellationToken = default);
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,25 @@
using ScadaLink.Commons.Entities.InboundApi;
namespace ScadaLink.Commons.Interfaces.Repositories;
public interface IInboundApiRepository
{
// ApiKey
Task<ApiKey?> GetApiKeyByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<ApiKey>> GetAllApiKeysAsync(CancellationToken cancellationToken = default);
Task<ApiKey?> GetApiKeyByValueAsync(string keyValue, CancellationToken cancellationToken = default);
Task AddApiKeyAsync(ApiKey apiKey, CancellationToken cancellationToken = default);
Task UpdateApiKeyAsync(ApiKey apiKey, CancellationToken cancellationToken = default);
Task DeleteApiKeyAsync(int id, CancellationToken cancellationToken = default);
// ApiMethod
Task<ApiMethod?> GetApiMethodByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<ApiMethod>> GetAllApiMethodsAsync(CancellationToken cancellationToken = default);
Task<ApiMethod?> GetMethodByNameAsync(string name, CancellationToken cancellationToken = default);
Task<IReadOnlyList<ApiKey>> GetApprovedKeysForMethodAsync(int methodId, CancellationToken cancellationToken = default);
Task AddApiMethodAsync(ApiMethod method, CancellationToken cancellationToken = default);
Task UpdateApiMethodAsync(ApiMethod method, CancellationToken cancellationToken = default);
Task DeleteApiMethodAsync(int id, CancellationToken cancellationToken = default);
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,30 @@
using ScadaLink.Commons.Entities.Notifications;
namespace ScadaLink.Commons.Interfaces.Repositories;
public interface INotificationRepository
{
// NotificationList
Task<NotificationList?> GetNotificationListByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<NotificationList>> GetAllNotificationListsAsync(CancellationToken cancellationToken = default);
Task<NotificationList?> GetListByNameAsync(string name, CancellationToken cancellationToken = default);
Task AddNotificationListAsync(NotificationList list, CancellationToken cancellationToken = default);
Task UpdateNotificationListAsync(NotificationList list, CancellationToken cancellationToken = default);
Task DeleteNotificationListAsync(int id, CancellationToken cancellationToken = default);
// NotificationRecipient
Task<NotificationRecipient?> GetRecipientByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<NotificationRecipient>> GetRecipientsByListIdAsync(int notificationListId, CancellationToken cancellationToken = default);
Task AddRecipientAsync(NotificationRecipient recipient, CancellationToken cancellationToken = default);
Task UpdateRecipientAsync(NotificationRecipient recipient, CancellationToken cancellationToken = default);
Task DeleteRecipientAsync(int id, CancellationToken cancellationToken = default);
// SmtpConfiguration
Task<SmtpConfiguration?> GetSmtpConfigurationByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<SmtpConfiguration>> GetAllSmtpConfigurationsAsync(CancellationToken cancellationToken = default);
Task AddSmtpConfigurationAsync(SmtpConfiguration configuration, CancellationToken cancellationToken = default);
Task UpdateSmtpConfigurationAsync(SmtpConfiguration configuration, CancellationToken cancellationToken = default);
Task DeleteSmtpConfigurationAsync(int id, CancellationToken cancellationToken = default);
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,23 @@
using ScadaLink.Commons.Entities.Security;
namespace ScadaLink.Commons.Interfaces.Repositories;
public interface ISecurityRepository
{
// LdapGroupMapping
Task<LdapGroupMapping?> GetMappingByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<LdapGroupMapping>> GetAllMappingsAsync(CancellationToken cancellationToken = default);
Task<IReadOnlyList<LdapGroupMapping>> GetMappingsByRoleAsync(string role, CancellationToken cancellationToken = default);
Task AddMappingAsync(LdapGroupMapping mapping, CancellationToken cancellationToken = default);
Task UpdateMappingAsync(LdapGroupMapping mapping, CancellationToken cancellationToken = default);
Task DeleteMappingAsync(int id, CancellationToken cancellationToken = default);
// SiteScopeRule
Task<SiteScopeRule?> GetScopeRuleByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<SiteScopeRule>> GetScopeRulesForMappingAsync(int ldapGroupMappingId, CancellationToken cancellationToken = default);
Task AddScopeRuleAsync(SiteScopeRule rule, CancellationToken cancellationToken = default);
Task UpdateScopeRuleAsync(SiteScopeRule rule, CancellationToken cancellationToken = default);
Task DeleteScopeRuleAsync(int id, CancellationToken cancellationToken = default);
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,74 @@
using ScadaLink.Commons.Entities.Instances;
using ScadaLink.Commons.Entities.Templates;
namespace ScadaLink.Commons.Interfaces.Repositories;
public interface ITemplateEngineRepository
{
// Template
Task<Template?> GetTemplateByIdAsync(int id, CancellationToken cancellationToken = default);
Task<Template?> GetTemplateWithChildrenAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<Template>> GetAllTemplatesAsync(CancellationToken cancellationToken = default);
Task AddTemplateAsync(Template template, CancellationToken cancellationToken = default);
Task UpdateTemplateAsync(Template template, CancellationToken cancellationToken = default);
Task DeleteTemplateAsync(int id, CancellationToken cancellationToken = default);
// TemplateAttribute
Task<TemplateAttribute?> GetTemplateAttributeByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<TemplateAttribute>> GetAttributesByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default);
Task AddTemplateAttributeAsync(TemplateAttribute attribute, CancellationToken cancellationToken = default);
Task UpdateTemplateAttributeAsync(TemplateAttribute attribute, CancellationToken cancellationToken = default);
Task DeleteTemplateAttributeAsync(int id, CancellationToken cancellationToken = default);
// TemplateAlarm
Task<TemplateAlarm?> GetTemplateAlarmByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<TemplateAlarm>> GetAlarmsByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default);
Task AddTemplateAlarmAsync(TemplateAlarm alarm, CancellationToken cancellationToken = default);
Task UpdateTemplateAlarmAsync(TemplateAlarm alarm, CancellationToken cancellationToken = default);
Task DeleteTemplateAlarmAsync(int id, CancellationToken cancellationToken = default);
// TemplateScript
Task<TemplateScript?> GetTemplateScriptByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<TemplateScript>> GetScriptsByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default);
Task AddTemplateScriptAsync(TemplateScript script, CancellationToken cancellationToken = default);
Task UpdateTemplateScriptAsync(TemplateScript script, CancellationToken cancellationToken = default);
Task DeleteTemplateScriptAsync(int id, CancellationToken cancellationToken = default);
// TemplateComposition
Task<TemplateComposition?> GetTemplateCompositionByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<TemplateComposition>> GetCompositionsByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default);
Task AddTemplateCompositionAsync(TemplateComposition composition, CancellationToken cancellationToken = default);
Task UpdateTemplateCompositionAsync(TemplateComposition composition, CancellationToken cancellationToken = default);
Task DeleteTemplateCompositionAsync(int id, CancellationToken cancellationToken = default);
// Instance
Task<Instance?> GetInstanceByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<Instance>> GetAllInstancesAsync(CancellationToken cancellationToken = default);
Task<IReadOnlyList<Instance>> GetInstancesByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default);
Task<IReadOnlyList<Instance>> GetInstancesBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
Task<Instance?> GetInstanceByUniqueNameAsync(string uniqueName, CancellationToken cancellationToken = default);
Task AddInstanceAsync(Instance instance, CancellationToken cancellationToken = default);
Task UpdateInstanceAsync(Instance instance, CancellationToken cancellationToken = default);
Task DeleteInstanceAsync(int id, CancellationToken cancellationToken = default);
// InstanceAttributeOverride
Task<IReadOnlyList<InstanceAttributeOverride>> GetOverridesByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
Task AddInstanceAttributeOverrideAsync(InstanceAttributeOverride attributeOverride, CancellationToken cancellationToken = default);
Task UpdateInstanceAttributeOverrideAsync(InstanceAttributeOverride attributeOverride, CancellationToken cancellationToken = default);
Task DeleteInstanceAttributeOverrideAsync(int id, CancellationToken cancellationToken = default);
// InstanceConnectionBinding
Task<IReadOnlyList<InstanceConnectionBinding>> GetBindingsByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
Task AddInstanceConnectionBindingAsync(InstanceConnectionBinding binding, CancellationToken cancellationToken = default);
Task UpdateInstanceConnectionBindingAsync(InstanceConnectionBinding binding, CancellationToken cancellationToken = default);
Task DeleteInstanceConnectionBindingAsync(int id, CancellationToken cancellationToken = default);
// Area
Task<Area?> GetAreaByIdAsync(int id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<Area>> GetAreasBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
Task AddAreaAsync(Area area, CancellationToken cancellationToken = default);
Task UpdateAreaAsync(Area area, CancellationToken cancellationToken = default);
Task DeleteAreaAsync(int id, CancellationToken cancellationToken = default);
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,6 @@
namespace ScadaLink.Commons.Interfaces.Services;
public interface IAuditService
{
Task LogAsync(string user, string action, string entityType, string entityId, string entityName, object? afterState, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,8 @@
namespace ScadaLink.Commons.Messages.Artifacts;
public record ArtifactDeploymentResponse(
string DeploymentId,
string SiteId,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,7 @@
namespace ScadaLink.Commons.Messages.Artifacts;
public record DatabaseConnectionArtifact(
string Name,
string ConnectionString,
int MaxRetries,
TimeSpan RetryDelay);

View File

@@ -0,0 +1,9 @@
namespace ScadaLink.Commons.Messages.Artifacts;
public record DeployArtifactsCommand(
string DeploymentId,
IReadOnlyList<SharedScriptArtifact>? SharedScripts,
IReadOnlyList<ExternalSystemArtifact>? ExternalSystems,
IReadOnlyList<DatabaseConnectionArtifact>? DatabaseConnections,
IReadOnlyList<NotificationListArtifact>? NotificationLists,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,8 @@
namespace ScadaLink.Commons.Messages.Artifacts;
public record ExternalSystemArtifact(
string Name,
string EndpointUrl,
string AuthType,
string? AuthConfiguration,
string? MethodDefinitionsJson);

View File

@@ -0,0 +1,5 @@
namespace ScadaLink.Commons.Messages.Artifacts;
public record NotificationListArtifact(
string Name,
IReadOnlyList<string> RecipientEmails);

View File

@@ -0,0 +1,7 @@
namespace ScadaLink.Commons.Messages.Artifacts;
public record SharedScriptArtifact(
string Name,
string Code,
string? ParameterDefinitions,
string? ReturnDefinition);

View File

@@ -0,0 +1,6 @@
namespace ScadaLink.Commons.Messages.Communication;
public record ConnectionStateChanged(
string SiteId,
bool IsConnected,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,6 @@
namespace ScadaLink.Commons.Messages.Communication;
public record RoutingMetadata(
string TargetSiteId,
string? TargetInstanceUniqueName,
string CorrelationId);

View File

@@ -0,0 +1,6 @@
namespace ScadaLink.Commons.Messages.Communication;
public record SiteIdentity(
string SiteId,
string NodeHostname,
bool IsActive);

View File

@@ -0,0 +1,9 @@
using ScadaLink.Commons.Messages.Streaming;
namespace ScadaLink.Commons.Messages.DebugView;
public record DebugViewSnapshot(
string InstanceUniqueName,
IReadOnlyList<AttributeValueChanged> AttributeValues,
IReadOnlyList<AlarmStateChanged> AlarmStates,
DateTimeOffset SnapshotTimestamp);

View File

@@ -0,0 +1,5 @@
namespace ScadaLink.Commons.Messages.DebugView;
public record SubscribeDebugViewRequest(
string InstanceUniqueName,
string CorrelationId);

View File

@@ -0,0 +1,5 @@
namespace ScadaLink.Commons.Messages.DebugView;
public record UnsubscribeDebugViewRequest(
string InstanceUniqueName,
string CorrelationId);

View File

@@ -0,0 +1,9 @@
namespace ScadaLink.Commons.Messages.Deployment;
public record DeployInstanceCommand(
string DeploymentId,
string InstanceUniqueName,
string RevisionHash,
string FlattenedConfigurationJson,
string DeployedBy,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,10 @@
using ScadaLink.Commons.Types.Enums;
namespace ScadaLink.Commons.Messages.Deployment;
public record DeploymentStatusResponse(
string DeploymentId,
string InstanceUniqueName,
DeploymentStatus Status,
string? ErrorMessage,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,6 @@
namespace ScadaLink.Commons.Messages.Deployment;
public record DeploymentValidationResult(
bool IsValid,
IReadOnlyList<string> Errors,
IReadOnlyList<string> Warnings);

View File

@@ -0,0 +1,7 @@
namespace ScadaLink.Commons.Messages.Deployment;
public record FlattenedConfigurationSnapshot(
string InstanceUniqueName,
string RevisionHash,
string ConfigurationJson,
DateTimeOffset GeneratedAt);

View File

@@ -0,0 +1,7 @@
namespace ScadaLink.Commons.Messages.Health;
public record HeartbeatMessage(
string SiteId,
string NodeHostname,
bool IsActive,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,14 @@
using ScadaLink.Commons.Types.Enums;
namespace ScadaLink.Commons.Messages.Health;
public record SiteHealthReport(
string SiteId,
long SequenceNumber,
DateTimeOffset ReportTimestamp,
IReadOnlyDictionary<string, ConnectionHealth> DataConnectionStatuses,
IReadOnlyDictionary<string, TagResolutionStatus> TagResolutionCounts,
int ScriptErrorCount,
int AlarmEvaluationErrorCount,
IReadOnlyDictionary<string, int> StoreAndForwardBufferDepths,
int DeadLetterCount);

View File

@@ -0,0 +1,3 @@
namespace ScadaLink.Commons.Messages.Health;
public record TagResolutionStatus(int TotalSubscribed, int SuccessfullyResolved);

View File

@@ -0,0 +1,6 @@
namespace ScadaLink.Commons.Messages.Lifecycle;
public record DeleteInstanceCommand(
string CommandId,
string InstanceUniqueName,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,6 @@
namespace ScadaLink.Commons.Messages.Lifecycle;
public record DisableInstanceCommand(
string CommandId,
string InstanceUniqueName,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,6 @@
namespace ScadaLink.Commons.Messages.Lifecycle;
public record EnableInstanceCommand(
string CommandId,
string InstanceUniqueName,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,8 @@
namespace ScadaLink.Commons.Messages.Lifecycle;
public record InstanceLifecycleResponse(
string CommandId,
string InstanceUniqueName,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,7 @@
namespace ScadaLink.Commons.Messages.ScriptExecution;
public record ScriptCallRequest(
string ScriptName,
IReadOnlyDictionary<string, object?>? Parameters,
int CurrentCallDepth,
string CorrelationId);

View File

@@ -0,0 +1,7 @@
namespace ScadaLink.Commons.Messages.ScriptExecution;
public record ScriptCallResult(
string CorrelationId,
bool Success,
object? ReturnValue,
string? ErrorMessage);

View File

@@ -0,0 +1,10 @@
using ScadaLink.Commons.Types.Enums;
namespace ScadaLink.Commons.Messages.Streaming;
public record AlarmStateChanged(
string InstanceUniqueName,
string AlarmName,
AlarmState State,
int Priority,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,9 @@
namespace ScadaLink.Commons.Messages.Streaming;
public record AttributeValueChanged(
string InstanceUniqueName,
string AttributePath,
string AttributeName,
object? Value,
string Quality,
DateTimeOffset Timestamp);

View File

@@ -0,0 +1,7 @@
namespace ScadaLink.Commons.Types.Enums;
public enum AlarmState
{
Active,
Normal
}

View File

@@ -0,0 +1,8 @@
namespace ScadaLink.Commons.Types.Enums;
public enum AlarmTriggerType
{
ValueMatch,
RangeViolation,
RateOfChange
}

View File

@@ -0,0 +1,9 @@
namespace ScadaLink.Commons.Types.Enums;
public enum ConnectionHealth
{
Connected,
Disconnected,
Connecting,
Error
}

View File

@@ -0,0 +1,12 @@
namespace ScadaLink.Commons.Types.Enums;
public enum DataType
{
Boolean,
Int32,
Float,
Double,
String,
DateTime,
Binary
}

View File

@@ -0,0 +1,9 @@
namespace ScadaLink.Commons.Types.Enums;
public enum DeploymentStatus
{
Pending,
InProgress,
Success,
Failed
}

View File

@@ -0,0 +1,7 @@
namespace ScadaLink.Commons.Types.Enums;
public enum InstanceState
{
Enabled,
Disabled
}

View File

@@ -0,0 +1,40 @@
namespace ScadaLink.Commons.Types;
public sealed class Result<T>
{
private readonly T? _value;
private readonly string? _error;
private Result(T value)
{
_value = value;
_error = null;
IsSuccess = true;
}
private Result(string error)
{
_value = default;
_error = error;
IsSuccess = false;
}
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
public T Value => IsSuccess
? _value!
: throw new InvalidOperationException("Cannot access Value on a failed Result. Error: " + _error);
public string Error => IsFailure
? _error!
: throw new InvalidOperationException("Cannot access Error on a successful Result.");
public static Result<T> Success(T value) => new(value);
public static Result<T> Failure(string error) => new(error);
public TResult Match<TResult>(Func<T, TResult> onSuccess, Func<string, TResult> onFailure) =>
IsSuccess ? onSuccess(_value!) : onFailure(_error!);
}

View File

@@ -0,0 +1,3 @@
namespace ScadaLink.Commons.Types;
public record RetryPolicy(int MaxRetries, TimeSpan Delay);