docs: add XML doc comments across src + Sister Projects section in CLAUDE.md
Bulk CommentChecker pass: fills in <param>/<inheritdoc> tags on public APIs across all 23 src/ projects so the doc-coverage gate is green. Also adds a Sister Projects section to CLAUDE.md pointing at the MxAccess Gateway and OtOpcUa sibling repos, and gitignores local credential captures (*login*.txt) and the wonder-app-vd03 deploy/ artifacts.
This commit is contained in:
@@ -2,16 +2,33 @@ namespace ScadaLink.Commons.Entities.Audit;
|
||||
|
||||
public class AuditLogEntry
|
||||
{
|
||||
/// <summary>Auto-incremented primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Username of the actor who performed the action.</summary>
|
||||
public string User { get; set; }
|
||||
/// <summary>Action performed (e.g. Created, Updated, Deleted).</summary>
|
||||
public string Action { get; set; }
|
||||
/// <summary>Entity type name (e.g. Template, ExternalSystem).</summary>
|
||||
public string EntityType { get; set; }
|
||||
/// <summary>String representation of the entity's primary key.</summary>
|
||||
public string EntityId { get; set; }
|
||||
/// <summary>Human-readable name of the affected entity.</summary>
|
||||
public string EntityName { get; set; }
|
||||
/// <summary>JSON snapshot of the entity's state after the action; null for deletes.</summary>
|
||||
public string? AfterStateJson { get; set; }
|
||||
/// <summary>UTC timestamp when the audit entry was recorded.</summary>
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
/// <summary>Bundle import session id when this entry was created during a bundle import; otherwise null.</summary>
|
||||
public Guid? BundleImportId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an audit log entry for the specified user action on a named entity.
|
||||
/// </summary>
|
||||
/// <param name="user">Username of the actor performing the action.</param>
|
||||
/// <param name="action">Action name (e.g. Created, Updated, Deleted).</param>
|
||||
/// <param name="entityType">Entity type name.</param>
|
||||
/// <param name="entityId">String primary key of the affected entity.</param>
|
||||
/// <param name="entityName">Human-readable name of the affected entity.</param>
|
||||
public AuditLogEntry(string user, string action, string entityType, string entityId, string entityName)
|
||||
{
|
||||
User = user ?? throw new ArgumentNullException(nameof(user));
|
||||
|
||||
@@ -6,9 +6,13 @@ namespace ScadaLink.Commons.Entities.Deployment;
|
||||
/// </summary>
|
||||
public class DeployedConfigSnapshot
|
||||
{
|
||||
/// <summary>Primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Foreign key to the owning <c>Instance</c> entity.</summary>
|
||||
public int InstanceId { get; set; }
|
||||
/// <summary>Unique deployment identifier assigned at deploy time for idempotency.</summary>
|
||||
public string DeploymentId { get; set; }
|
||||
/// <summary>Revision hash of the flattened configuration at deploy time, used for staleness detection.</summary>
|
||||
public string RevisionHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -16,8 +20,13 @@ public class DeployedConfigSnapshot
|
||||
/// </summary>
|
||||
public string ConfigurationJson { get; set; }
|
||||
|
||||
/// <summary>UTC timestamp when this snapshot was persisted.</summary>
|
||||
public DateTimeOffset DeployedAt { get; set; }
|
||||
|
||||
/// <summary>Initializes a new snapshot with the deployment identity, revision hash, and serialized configuration.</summary>
|
||||
/// <param name="deploymentId">Unique deployment identifier.</param>
|
||||
/// <param name="revisionHash">Revision hash of the flattened configuration.</param>
|
||||
/// <param name="configurationJson">JSON-serialized flattened configuration.</param>
|
||||
public DeployedConfigSnapshot(string deploymentId, string revisionHash, string configurationJson)
|
||||
{
|
||||
DeploymentId = deploymentId ?? throw new ArgumentNullException(nameof(deploymentId));
|
||||
|
||||
@@ -4,14 +4,49 @@ namespace ScadaLink.Commons.Entities.Deployment;
|
||||
|
||||
public class DeploymentRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// The deployment record identifier.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The instance identifier being deployed.
|
||||
/// </summary>
|
||||
public int InstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current deployment status.
|
||||
/// </summary>
|
||||
public DeploymentStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The deployment identifier.
|
||||
/// </summary>
|
||||
public string DeploymentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The revision hash of the deployed configuration, or null.
|
||||
/// </summary>
|
||||
public string? RevisionHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user who initiated the deployment.
|
||||
/// </summary>
|
||||
public string DeployedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time when the deployment was initiated.
|
||||
/// </summary>
|
||||
public DateTimeOffset DeployedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time when the deployment completed, or null if still in progress.
|
||||
/// </summary>
|
||||
public DateTimeOffset? CompletedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message if the deployment failed, or null.
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -19,6 +54,11 @@ public class DeploymentRecord
|
||||
/// </summary>
|
||||
public byte[] RowVersion { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the DeploymentRecord.
|
||||
/// </summary>
|
||||
/// <param name="deploymentId">The deployment identifier.</param>
|
||||
/// <param name="deployedBy">The user initiating the deployment.</param>
|
||||
public DeploymentRecord(string deploymentId, string deployedBy)
|
||||
{
|
||||
DeploymentId = deploymentId ?? throw new ArgumentNullException(nameof(deploymentId));
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
namespace ScadaLink.Commons.Entities.Deployment;
|
||||
|
||||
/// <summary>
|
||||
/// Records a system-wide artifact deployment operation, tracking status per site.
|
||||
/// </summary>
|
||||
public class SystemArtifactDeploymentRecord
|
||||
{
|
||||
/// <summary>Primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Type identifier for the deployed artifact (e.g. the artifact category name).</summary>
|
||||
public string ArtifactType { get; set; }
|
||||
/// <summary>Username of the operator who initiated the deployment.</summary>
|
||||
public string DeployedBy { get; set; }
|
||||
/// <summary>UTC timestamp when the deployment was initiated.</summary>
|
||||
public DateTimeOffset DeployedAt { get; set; }
|
||||
/// <summary>JSON-serialized per-site deployment status map, or null if not yet computed.</summary>
|
||||
public string? PerSiteStatus { get; set; }
|
||||
|
||||
/// <summary>Initializes a new <see cref="SystemArtifactDeploymentRecord"/> with required fields.</summary>
|
||||
/// <param name="artifactType">The artifact type being deployed.</param>
|
||||
/// <param name="deployedBy">The username of the initiating operator.</param>
|
||||
public SystemArtifactDeploymentRecord(string artifactType, string deployedBy)
|
||||
{
|
||||
ArtifactType = artifactType ?? throw new ArgumentNullException(nameof(artifactType));
|
||||
|
||||
@@ -2,12 +2,20 @@ namespace ScadaLink.Commons.Entities.ExternalSystems;
|
||||
|
||||
public class DatabaseConnectionDefinition
|
||||
{
|
||||
/// <summary>Gets or sets the primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Gets or sets the human-readable connection name.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>Gets or sets the ADO.NET connection string for this database.</summary>
|
||||
public string ConnectionString { get; set; }
|
||||
/// <summary>Gets or sets the maximum number of retry attempts for transient failures.</summary>
|
||||
public int MaxRetries { get; set; }
|
||||
/// <summary>Gets or sets the delay between retry attempts.</summary>
|
||||
public TimeSpan RetryDelay { get; set; }
|
||||
|
||||
/// <summary>Initializes a new <see cref="DatabaseConnectionDefinition"/> with the required name and connection string.</summary>
|
||||
/// <param name="name">The human-readable connection name.</param>
|
||||
/// <param name="connectionString">The ADO.NET connection string.</param>
|
||||
public DatabaseConnectionDefinition(string name, string connectionString)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -2,14 +2,27 @@ namespace ScadaLink.Commons.Entities.ExternalSystems;
|
||||
|
||||
public class ExternalSystemDefinition
|
||||
{
|
||||
/// <summary>Database primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Display name for the external system.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>Base URL of the external system's HTTP endpoint.</summary>
|
||||
public string EndpointUrl { get; set; }
|
||||
/// <summary>Authentication type identifier (e.g., "ApiKey", "Basic").</summary>
|
||||
public string AuthType { get; set; }
|
||||
/// <summary>JSON-serialized authentication configuration for the selected <see cref="AuthType"/>.</summary>
|
||||
public string? AuthConfiguration { get; set; }
|
||||
/// <summary>Maximum number of retry attempts for transient failures.</summary>
|
||||
public int MaxRetries { get; set; }
|
||||
/// <summary>Fixed delay between retry attempts.</summary>
|
||||
public TimeSpan RetryDelay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="ExternalSystemDefinition"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">Display name for the external system.</param>
|
||||
/// <param name="endpointUrl">Base URL of the external system's HTTP endpoint.</param>
|
||||
/// <param name="authType">Authentication type identifier.</param>
|
||||
public ExternalSystemDefinition(string name, string endpointUrl, string authType)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
namespace ScadaLink.Commons.Entities.ExternalSystems;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a callable HTTP method on an external system definition.
|
||||
/// </summary>
|
||||
public class ExternalSystemMethod
|
||||
{
|
||||
/// <summary>Primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Foreign key referencing the owning <c>ExternalSystemDefinition</c>.</summary>
|
||||
public int ExternalSystemDefinitionId { get; set; }
|
||||
/// <summary>Name of the method as referenced in scripts.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>HTTP method (GET, POST, PUT, DELETE, etc.).</summary>
|
||||
public string HttpMethod { get; set; }
|
||||
/// <summary>URL path relative to the external system's base URL.</summary>
|
||||
public string Path { get; set; }
|
||||
/// <summary>JSON-serialized parameter definitions for this method, or null if there are none.</summary>
|
||||
public string? ParameterDefinitions { get; set; }
|
||||
/// <summary>JSON-serialized return type definition for this method, or null if void.</summary>
|
||||
public string? ReturnDefinition { get; set; }
|
||||
|
||||
/// <summary>Initializes a new instance of <see cref="ExternalSystemMethod"/> with the required fields.</summary>
|
||||
/// <param name="name">The method name.</param>
|
||||
/// <param name="httpMethod">The HTTP method verb.</param>
|
||||
/// <param name="path">The URL path.</param>
|
||||
public ExternalSystemMethod(string name, string httpMethod, string path)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -10,7 +10,9 @@ namespace ScadaLink.Commons.Entities.InboundApi;
|
||||
/// </summary>
|
||||
public class ApiKey
|
||||
{
|
||||
/// <summary>Database primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Display name for the API key.</summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -20,6 +22,7 @@ public class ApiKey
|
||||
/// </summary>
|
||||
public string KeyHash { get; set; }
|
||||
|
||||
/// <summary>When false, the key is rejected even if the hash matches.</summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -28,6 +31,8 @@ public class ApiKey
|
||||
/// never holds the plaintext. Production code paths that have a configured pepper
|
||||
/// should use <see cref="FromHash(string, string)"/> with a peppered hash instead.
|
||||
/// </summary>
|
||||
/// <param name="name">Display name for the API key.</param>
|
||||
/// <param name="keyValue">Plaintext key value; hashed immediately and never stored.</param>
|
||||
public ApiKey(string name, string keyValue)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
@@ -50,6 +55,8 @@ public class ApiKey
|
||||
/// path, which generates a random key, hashes it with the configured (peppered)
|
||||
/// <see cref="IApiKeyHasher"/>, and stores only the resulting hash.
|
||||
/// </summary>
|
||||
/// <param name="name">Display name for the API key.</param>
|
||||
/// <param name="keyHash">Pre-computed keyed hash of the API key value.</param>
|
||||
public static ApiKey FromHash(string name, string keyHash)
|
||||
{
|
||||
return new ApiKey
|
||||
|
||||
@@ -2,14 +2,24 @@ namespace ScadaLink.Commons.Entities.InboundApi;
|
||||
|
||||
public class ApiMethod
|
||||
{
|
||||
/// <summary>Gets or sets the primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Gets or sets the method name used in the route (<c>/api/{Name}</c>).</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>Gets or sets the C# script body executed when the method is invoked.</summary>
|
||||
public string Script { get; set; }
|
||||
/// <summary>Gets or sets the JSON-serialised list of API key IDs approved for this method, or <c>null</c> for unrestricted.</summary>
|
||||
public string? ApprovedApiKeyIds { get; set; }
|
||||
/// <summary>Gets or sets the JSON Schema describing the accepted parameters, or <c>null</c> if the method takes no parameters.</summary>
|
||||
public string? ParameterDefinitions { get; set; }
|
||||
/// <summary>Gets or sets the JSON Schema describing the return type, or <c>null</c> if the method returns nothing.</summary>
|
||||
public string? ReturnDefinition { get; set; }
|
||||
/// <summary>Gets or sets the script execution timeout in seconds.</summary>
|
||||
public int TimeoutSeconds { get; set; }
|
||||
|
||||
/// <summary>Initializes a new <see cref="ApiMethod"/> with the required name and script.</summary>
|
||||
/// <param name="name">The method name (used as the route segment).</param>
|
||||
/// <param name="script">The C# script body to execute.</param>
|
||||
public ApiMethod(string name, string script)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -2,12 +2,21 @@ namespace ScadaLink.Commons.Entities.Instances;
|
||||
|
||||
public class Area
|
||||
{
|
||||
/// <summary>Gets or sets the database primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Gets or sets the id of the site this area belongs to.</summary>
|
||||
public int SiteId { get; set; }
|
||||
/// <summary>Gets or sets the display name of the area.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>Gets or sets the id of the parent area, or null for a root area.</summary>
|
||||
public int? ParentAreaId { get; set; }
|
||||
/// <summary>Gets or sets the child areas nested under this area.</summary>
|
||||
public ICollection<Area> Children { get; set; } = new List<Area>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="Area"/> with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name">Display name for the area.</param>
|
||||
public Area(string name)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -4,16 +4,29 @@ namespace ScadaLink.Commons.Entities.Instances;
|
||||
|
||||
public class Instance
|
||||
{
|
||||
/// <summary>Primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Foreign key to the template this instance is based on.</summary>
|
||||
public int TemplateId { get; set; }
|
||||
/// <summary>Foreign key to the site where this instance is deployed.</summary>
|
||||
public int SiteId { get; set; }
|
||||
/// <summary>Optional foreign key to the organisational area this instance belongs to.</summary>
|
||||
public int? AreaId { get; set; }
|
||||
/// <summary>System-wide unique name that identifies this instance.</summary>
|
||||
public string UniqueName { get; set; }
|
||||
/// <summary>Current lifecycle state of the instance.</summary>
|
||||
public InstanceState State { get; set; }
|
||||
/// <summary>Per-attribute value overrides applied on top of the template defaults.</summary>
|
||||
public ICollection<InstanceAttributeOverride> AttributeOverrides { get; set; } = new List<InstanceAttributeOverride>();
|
||||
/// <summary>Per-alarm configuration overrides applied on top of the template defaults.</summary>
|
||||
public ICollection<InstanceAlarmOverride> AlarmOverrides { get; set; } = new List<InstanceAlarmOverride>();
|
||||
/// <summary>Data-connection bindings that map template tags to site data sources.</summary>
|
||||
public ICollection<InstanceConnectionBinding> ConnectionBindings { get; set; } = new List<InstanceConnectionBinding>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance with the required unique name.
|
||||
/// </summary>
|
||||
/// <param name="uniqueName">System-wide unique name for this instance.</param>
|
||||
public Instance(string uniqueName)
|
||||
{
|
||||
UniqueName = uniqueName ?? throw new ArgumentNullException(nameof(uniqueName));
|
||||
|
||||
@@ -19,7 +19,9 @@ namespace ScadaLink.Commons.Entities.Instances;
|
||||
/// </summary>
|
||||
public class InstanceAlarmOverride
|
||||
{
|
||||
/// <summary>Primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Foreign key to the instance this override belongs to.</summary>
|
||||
public int InstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -41,6 +43,10 @@ public class InstanceAlarmOverride
|
||||
/// </summary>
|
||||
public int? PriorityLevelOverride { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new alarm override for the specified alarm.
|
||||
/// </summary>
|
||||
/// <param name="alarmCanonicalName">Canonical name of the alarm to override.</param>
|
||||
public InstanceAlarmOverride(string alarmCanonicalName)
|
||||
{
|
||||
AlarmCanonicalName = alarmCanonicalName ?? throw new ArgumentNullException(nameof(alarmCanonicalName));
|
||||
|
||||
@@ -2,11 +2,17 @@ namespace ScadaLink.Commons.Entities.Instances;
|
||||
|
||||
public class InstanceAttributeOverride
|
||||
{
|
||||
/// <summary>Gets or sets the primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Gets or sets the foreign key of the owning instance.</summary>
|
||||
public int InstanceId { get; set; }
|
||||
/// <summary>Gets or sets the attribute name this override targets.</summary>
|
||||
public string AttributeName { get; set; }
|
||||
/// <summary>Gets or sets the override value, or <c>null</c> to clear a previous override.</summary>
|
||||
public string? OverrideValue { get; set; }
|
||||
|
||||
/// <summary>Initializes a new <see cref="InstanceAttributeOverride"/> for the given attribute name.</summary>
|
||||
/// <param name="attributeName">The name of the attribute to override.</param>
|
||||
public InstanceAttributeOverride(string attributeName)
|
||||
{
|
||||
AttributeName = attributeName ?? throw new ArgumentNullException(nameof(attributeName));
|
||||
|
||||
@@ -2,11 +2,19 @@ namespace ScadaLink.Commons.Entities.Instances;
|
||||
|
||||
public class InstanceConnectionBinding
|
||||
{
|
||||
/// <summary>Auto-incremented primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Foreign key to the owning instance.</summary>
|
||||
public int InstanceId { get; set; }
|
||||
/// <summary>Name of the attribute on the instance that this binding maps to a data connection tag.</summary>
|
||||
public string AttributeName { get; set; }
|
||||
/// <summary>Foreign key to the data connection that provides values for this attribute.</summary>
|
||||
public int DataConnectionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a binding for the specified attribute name.
|
||||
/// </summary>
|
||||
/// <param name="attributeName">Name of the attribute being bound to a data connection.</param>
|
||||
public InstanceConnectionBinding(string attributeName)
|
||||
{
|
||||
AttributeName = attributeName ?? throw new ArgumentNullException(nameof(attributeName));
|
||||
|
||||
@@ -11,19 +11,27 @@ public class Notification
|
||||
{
|
||||
/// <summary>GUID primary key, generated at the originating site.</summary>
|
||||
public string NotificationId { get; set; }
|
||||
/// <summary>Gets or sets the notification type.</summary>
|
||||
public NotificationType Type { get; set; }
|
||||
/// <summary>Gets or sets the notification list name.</summary>
|
||||
public string ListName { get; set; }
|
||||
/// <summary>Gets or sets the notification subject.</summary>
|
||||
public string Subject { get; set; }
|
||||
/// <summary>Gets or sets the notification body.</summary>
|
||||
public string Body { get; set; }
|
||||
|
||||
/// <summary>JSON extensibility hook for channel-specific payload data.</summary>
|
||||
public string? TypeData { get; set; }
|
||||
/// <summary>Gets or sets the notification delivery status.</summary>
|
||||
public NotificationStatus Status { get; set; } = NotificationStatus.Pending;
|
||||
/// <summary>Gets or sets the delivery retry count.</summary>
|
||||
public int RetryCount { get; set; }
|
||||
/// <summary>Gets or sets the last error message, if any.</summary>
|
||||
public string? LastError { get; set; }
|
||||
|
||||
/// <summary>Resolved delivery targets snapshotted at delivery time, for audit.</summary>
|
||||
public string? ResolvedTargets { get; set; }
|
||||
/// <summary>Gets or sets the originating site ID.</summary>
|
||||
public string SourceSiteId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -34,7 +42,9 @@ public class Notification
|
||||
/// central; nullable so rows submitted before the column existed don't block ingest.
|
||||
/// </summary>
|
||||
public string? SourceNode { get; set; }
|
||||
/// <summary>Gets or sets the originating instance ID, if any.</summary>
|
||||
public string? SourceInstanceId { get; set; }
|
||||
/// <summary>Gets or sets the originating script name, if any.</summary>
|
||||
public string? SourceScript { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -54,14 +64,27 @@ public class Notification
|
||||
/// non-routed runs, or for notifications submitted before the column existed.
|
||||
/// </summary>
|
||||
public Guid? OriginParentExecutionId { get; set; }
|
||||
/// <summary>Gets or sets the time when the notification was enqueued at the site.</summary>
|
||||
public DateTimeOffset SiteEnqueuedAt { get; set; }
|
||||
|
||||
/// <summary>Central ingest time.</summary>
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
/// <summary>Gets or sets the time of the last delivery attempt, if any.</summary>
|
||||
public DateTimeOffset? LastAttemptAt { get; set; }
|
||||
/// <summary>Gets or sets the time of the next scheduled delivery attempt, if any.</summary>
|
||||
public DateTimeOffset? NextAttemptAt { get; set; }
|
||||
/// <summary>Gets or sets the time the notification was delivered, if any.</summary>
|
||||
public DateTimeOffset? DeliveredAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Notification class.
|
||||
/// </summary>
|
||||
/// <param name="notificationId">The notification ID (GUID).</param>
|
||||
/// <param name="type">The notification type.</param>
|
||||
/// <param name="listName">The notification list name.</param>
|
||||
/// <param name="subject">The notification subject.</param>
|
||||
/// <param name="body">The notification body text.</param>
|
||||
/// <param name="sourceSiteId">The originating site ID.</param>
|
||||
public Notification(string notificationId, NotificationType type, string listName,
|
||||
string subject, string body, string sourceSiteId)
|
||||
{
|
||||
|
||||
@@ -4,11 +4,17 @@ namespace ScadaLink.Commons.Entities.Notifications;
|
||||
|
||||
public class NotificationList
|
||||
{
|
||||
/// <summary>Primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Display name of the notification list.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>Delivery type discriminator (e.g., Email).</summary>
|
||||
public NotificationType Type { get; set; } = NotificationType.Email;
|
||||
/// <summary>Recipients belonging to this list.</summary>
|
||||
public ICollection<NotificationRecipient> Recipients { get; set; } = new List<NotificationRecipient>();
|
||||
|
||||
/// <summary>Initializes the notification list with the given name.</summary>
|
||||
/// <param name="name">Display name of the notification list.</param>
|
||||
public NotificationList(string name)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -2,11 +2,20 @@ namespace ScadaLink.Commons.Entities.Notifications;
|
||||
|
||||
public class NotificationRecipient
|
||||
{
|
||||
/// <summary>Gets or sets the database primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Gets or sets the id of the parent notification list.</summary>
|
||||
public int NotificationListId { get; set; }
|
||||
/// <summary>Gets or sets the display name of the recipient.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>Gets or sets the recipient's email address.</summary>
|
||||
public string EmailAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="NotificationRecipient"/> with the required fields.
|
||||
/// </summary>
|
||||
/// <param name="name">Display name of the recipient.</param>
|
||||
/// <param name="emailAddress">Email address of the recipient.</param>
|
||||
public NotificationRecipient(string name, string emailAddress)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -2,18 +2,35 @@ namespace ScadaLink.Commons.Entities.Notifications;
|
||||
|
||||
public class SmtpConfiguration
|
||||
{
|
||||
/// <summary>Gets or sets the primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Gets or sets the SMTP server hostname or IP address.</summary>
|
||||
public string Host { get; set; }
|
||||
/// <summary>Gets or sets the SMTP server port number.</summary>
|
||||
public int Port { get; set; }
|
||||
/// <summary>Gets or sets the authentication type (e.g. Basic, OAuth2ClientCredentials).</summary>
|
||||
public string AuthType { get; set; }
|
||||
/// <summary>Gets or sets the serialized credentials (password or OAuth2 client secret), or null when not applicable.</summary>
|
||||
public string? Credentials { get; set; }
|
||||
/// <summary>Gets or sets the TLS mode (None, StartTLS, or SSL), or null to use the provider default.</summary>
|
||||
public string? TlsMode { get; set; }
|
||||
/// <summary>Gets or sets the sender address placed in the From header.</summary>
|
||||
public string FromAddress { get; set; }
|
||||
/// <summary>Gets or sets the connection timeout in seconds.</summary>
|
||||
public int ConnectionTimeoutSeconds { get; set; }
|
||||
/// <summary>Gets or sets the maximum number of concurrent SMTP connections.</summary>
|
||||
public int MaxConcurrentConnections { get; set; }
|
||||
/// <summary>Gets or sets the maximum number of delivery retries before parking.</summary>
|
||||
public int MaxRetries { get; set; }
|
||||
/// <summary>Gets or sets the delay between retry attempts.</summary>
|
||||
public TimeSpan RetryDelay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="SmtpConfiguration"/> with required fields.
|
||||
/// </summary>
|
||||
/// <param name="host">SMTP server hostname or IP address.</param>
|
||||
/// <param name="authType">Authentication type string (e.g. Basic, OAuth2ClientCredentials).</param>
|
||||
/// <param name="fromAddress">Sender address for the From header.</param>
|
||||
public SmtpConfiguration(string host, string authType, string fromAddress)
|
||||
{
|
||||
Host = host ?? throw new ArgumentNullException(nameof(host));
|
||||
|
||||
@@ -2,12 +2,22 @@ namespace ScadaLink.Commons.Entities.Scripts;
|
||||
|
||||
public class SharedScript
|
||||
{
|
||||
/// <summary>Primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Unique script name used to reference this script from templates.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>C# script source code.</summary>
|
||||
public string Code { get; set; }
|
||||
/// <summary>JSON-serialized parameter definitions, or null when the script takes no parameters.</summary>
|
||||
public string? ParameterDefinitions { get; set; }
|
||||
/// <summary>JSON-serialized return type definition, or null when the script has no return value.</summary>
|
||||
public string? ReturnDefinition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new shared script with the required name and code.
|
||||
/// </summary>
|
||||
/// <param name="name">Unique script name.</param>
|
||||
/// <param name="code">C# script source code.</param>
|
||||
public SharedScript(string name, string code)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -2,13 +2,19 @@ namespace ScadaLink.Commons.Entities.Security;
|
||||
|
||||
public class LdapGroupMapping
|
||||
{
|
||||
/// <summary>Gets or sets the primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Gets or sets the LDAP/AD group CN that this mapping targets.</summary>
|
||||
public string LdapGroupName { get; set; }
|
||||
/// <summary>Gets or sets the ScadaLink role name this group maps to.</summary>
|
||||
public string Role { get; set; }
|
||||
|
||||
// Parameterless constructor for EF Core seed data
|
||||
private LdapGroupMapping() { LdapGroupName = null!; Role = null!; }
|
||||
|
||||
/// <summary>Initializes a new <see cref="LdapGroupMapping"/> linking an LDAP group to a ScadaLink role.</summary>
|
||||
/// <param name="ldapGroupName">The LDAP group name (CN).</param>
|
||||
/// <param name="role">The ScadaLink role name to assign.</param>
|
||||
public LdapGroupMapping(string ldapGroupName, string role)
|
||||
{
|
||||
LdapGroupName = ldapGroupName ?? throw new ArgumentNullException(nameof(ldapGroupName));
|
||||
|
||||
@@ -2,7 +2,10 @@ namespace ScadaLink.Commons.Entities.Security;
|
||||
|
||||
public class SiteScopeRule
|
||||
{
|
||||
/// <summary>Database primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Foreign key to the <see cref="LdapGroupMapping"/> this rule restricts.</summary>
|
||||
public int LdapGroupMappingId { get; set; }
|
||||
/// <summary>Foreign key to the site this rule limits the mapping to.</summary>
|
||||
public int SiteId { get; set; }
|
||||
}
|
||||
|
||||
@@ -2,14 +2,27 @@ namespace ScadaLink.Commons.Entities.Sites;
|
||||
|
||||
public class DataConnection
|
||||
{
|
||||
/// <summary>Gets or sets the database primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Gets or sets the owning site's id.</summary>
|
||||
public int SiteId { get; set; }
|
||||
/// <summary>Gets or sets the unique name of this data connection within the site.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>Gets or sets the protocol type string (e.g. "OpcUa").</summary>
|
||||
public string Protocol { get; set; }
|
||||
/// <summary>Gets or sets the primary protocol-specific configuration JSON.</summary>
|
||||
public string? PrimaryConfiguration { get; set; }
|
||||
/// <summary>Gets or sets the backup protocol-specific configuration JSON used on failover.</summary>
|
||||
public string? BackupConfiguration { get; set; }
|
||||
/// <summary>Gets or sets the number of failover retry attempts before the connection is marked failed.</summary>
|
||||
public int FailoverRetryCount { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="DataConnection"/> with the required fields.
|
||||
/// </summary>
|
||||
/// <param name="name">Unique name of the connection within the site.</param>
|
||||
/// <param name="protocol">Protocol type string (e.g. "OpcUa").</param>
|
||||
/// <param name="siteId">Id of the owning site.</param>
|
||||
public DataConnection(string name, string protocol, int siteId)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -2,15 +2,28 @@ namespace ScadaLink.Commons.Entities.Sites;
|
||||
|
||||
public class Site
|
||||
{
|
||||
/// <summary>Primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Human-readable display name for the site.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>Machine-readable identifier used in Akka addresses and API routing.</summary>
|
||||
public string SiteIdentifier { get; set; }
|
||||
/// <summary>Optional description of the site.</summary>
|
||||
public string? Description { get; set; }
|
||||
/// <summary>Akka remote address for site node A (ClusterClient contact point).</summary>
|
||||
public string? NodeAAddress { get; set; }
|
||||
/// <summary>Akka remote address for site node B (ClusterClient contact point).</summary>
|
||||
public string? NodeBAddress { get; set; }
|
||||
/// <summary>gRPC endpoint for site node A used by the central SiteStreamGrpcClient.</summary>
|
||||
public string? GrpcNodeAAddress { get; set; }
|
||||
/// <summary>gRPC endpoint for site node B used by the central SiteStreamGrpcClient.</summary>
|
||||
public string? GrpcNodeBAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new site with the required name and identifier.
|
||||
/// </summary>
|
||||
/// <param name="name">Human-readable display name.</param>
|
||||
/// <param name="siteIdentifier">Machine-readable identifier used in Akka addresses.</param>
|
||||
public Site(string name, string siteIdentifier)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -2,14 +2,41 @@ namespace ScadaLink.Commons.Entities.Templates;
|
||||
|
||||
public class Template
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique identifier for the template.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// The name of the template.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Optional description of the template.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
/// <summary>
|
||||
/// The identifier of the parent template, if this template inherits from another.
|
||||
/// </summary>
|
||||
public int? ParentTemplateId { get; set; }
|
||||
/// <summary>
|
||||
/// The identifier of the folder containing this template.
|
||||
/// </summary>
|
||||
public int? FolderId { get; set; }
|
||||
/// <summary>
|
||||
/// Collection of attributes defined in this template.
|
||||
/// </summary>
|
||||
public ICollection<TemplateAttribute> Attributes { get; set; } = new List<TemplateAttribute>();
|
||||
/// <summary>
|
||||
/// Collection of alarms defined in this template.
|
||||
/// </summary>
|
||||
public ICollection<TemplateAlarm> Alarms { get; set; } = new List<TemplateAlarm>();
|
||||
/// <summary>
|
||||
/// Collection of scripts defined in this template.
|
||||
/// </summary>
|
||||
public ICollection<TemplateScript> Scripts { get; set; } = new List<TemplateScript>();
|
||||
/// <summary>
|
||||
/// Collection of compositions defined in this template.
|
||||
/// </summary>
|
||||
public ICollection<TemplateComposition> Compositions { get; set; } = new List<TemplateComposition>();
|
||||
|
||||
/// <summary>
|
||||
@@ -27,6 +54,10 @@ public class Template
|
||||
/// </summary>
|
||||
public int? OwnerCompositionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Template with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the template.</param>
|
||||
public Template(string name)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -4,14 +4,23 @@ namespace ScadaLink.Commons.Entities.Templates;
|
||||
|
||||
public class TemplateAlarm
|
||||
{
|
||||
/// <summary>Database primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Foreign key to the owning <see cref="Template"/>.</summary>
|
||||
public int TemplateId { get; set; }
|
||||
/// <summary>Unique alarm name within the template.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>Optional human-readable description of the alarm.</summary>
|
||||
public string? Description { get; set; }
|
||||
/// <summary>Alarm priority level; lower values indicate higher priority.</summary>
|
||||
public int PriorityLevel { get; set; }
|
||||
/// <summary>When true, this alarm is locked and cannot be overridden in derived templates.</summary>
|
||||
public bool IsLocked { get; set; }
|
||||
/// <summary>Type of trigger condition that activates this alarm.</summary>
|
||||
public AlarmTriggerType TriggerType { get; set; }
|
||||
/// <summary>JSON-serialized trigger configuration specific to the <see cref="TriggerType"/>.</summary>
|
||||
public string? TriggerConfiguration { get; set; }
|
||||
/// <summary>Optional ID of the script to execute when the alarm triggers.</summary>
|
||||
public int? OnTriggerScriptId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -29,6 +38,10 @@ public class TemplateAlarm
|
||||
/// </summary>
|
||||
public bool LockedInDerived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="TemplateAlarm"/> with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The unique alarm name within the template.</param>
|
||||
public TemplateAlarm(string name)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -4,13 +4,37 @@ namespace ScadaLink.Commons.Entities.Templates;
|
||||
|
||||
public class TemplateAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the attribute ID.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the template ID that owns this attribute.
|
||||
/// </summary>
|
||||
public int TemplateId { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the attribute name.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the attribute value.
|
||||
/// </summary>
|
||||
public string? Value { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the data type of the attribute.
|
||||
/// </summary>
|
||||
public DataType DataType { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the attribute is locked from override.
|
||||
/// </summary>
|
||||
public bool IsLocked { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the attribute description.
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the data source reference for this attribute.
|
||||
/// </summary>
|
||||
public string? DataSourceReference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -28,6 +52,10 @@ public class TemplateAttribute
|
||||
/// </summary>
|
||||
public bool LockedInDerived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TemplateAttribute"/> class with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The attribute name.</param>
|
||||
public TemplateAttribute(string name)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -2,11 +2,19 @@ namespace ScadaLink.Commons.Entities.Templates;
|
||||
|
||||
public class TemplateComposition
|
||||
{
|
||||
/// <summary>Primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Foreign key to the parent template that includes this composition.</summary>
|
||||
public int TemplateId { get; set; }
|
||||
/// <summary>Foreign key to the template being composed into the parent.</summary>
|
||||
public int ComposedTemplateId { get; set; }
|
||||
/// <summary>Name of the composition instance within the parent template's namespace.</summary>
|
||||
public string InstanceName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new template composition with the required instance name.
|
||||
/// </summary>
|
||||
/// <param name="instanceName">Name of this composition slot within the parent template.</param>
|
||||
public TemplateComposition(string instanceName)
|
||||
{
|
||||
InstanceName = instanceName ?? throw new ArgumentNullException(nameof(instanceName));
|
||||
|
||||
@@ -2,11 +2,19 @@ namespace ScadaLink.Commons.Entities.Templates;
|
||||
|
||||
public class TemplateFolder
|
||||
{
|
||||
/// <summary>Database primary key.</summary>
|
||||
public int Id { get; set; }
|
||||
/// <summary>Display name for the folder.</summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>ID of the parent folder, or null for root-level folders.</summary>
|
||||
public int? ParentFolderId { get; set; }
|
||||
/// <summary>Display ordering position within the parent folder.</summary>
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="TemplateFolder"/> with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The display name for the folder.</param>
|
||||
public TemplateFolder(string name)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -2,15 +2,54 @@ namespace ScadaLink.Commons.Entities.Templates;
|
||||
|
||||
public class TemplateScript
|
||||
{
|
||||
/// <summary>
|
||||
/// The script identifier.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The template identifier this script belongs to.
|
||||
/// </summary>
|
||||
public int TemplateId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The script name.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the script is locked for editing.
|
||||
/// </summary>
|
||||
public bool IsLocked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The script code.
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The trigger type for the script, or null.
|
||||
/// </summary>
|
||||
public string? TriggerType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The trigger configuration, or null.
|
||||
/// </summary>
|
||||
public string? TriggerConfiguration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parameter definitions in JSON format, or null.
|
||||
/// </summary>
|
||||
public string? ParameterDefinitions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The return type definition in JSON format, or null.
|
||||
/// </summary>
|
||||
public string? ReturnDefinition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The minimum time between script runs, or null.
|
||||
/// </summary>
|
||||
public TimeSpan? MinTimeBetweenRuns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -23,11 +62,16 @@ public class TemplateScript
|
||||
|
||||
/// <summary>
|
||||
/// Set on a base script. When true, derived templates may not override
|
||||
/// the script body — the row is rendered readonly with a 🔒 in the derived
|
||||
/// the script body — the row is rendered readonly in the derived
|
||||
/// UI, and any attempt to update it through the API is rejected.
|
||||
/// </summary>
|
||||
public bool LockedInDerived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the TemplateScript.
|
||||
/// </summary>
|
||||
/// <param name="name">The script name.</param>
|
||||
/// <param name="code">The script code.</param>
|
||||
public TemplateScript(string name, string code)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
|
||||
@@ -34,6 +34,13 @@ public interface IOperationTrackingStore
|
||||
/// untouched), matching the at-least-once semantics of the calling site
|
||||
/// store-and-forward path.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique operation ID (idempotency key).</param>
|
||||
/// <param name="kind">Kind of operation (e.g., cached call type).</param>
|
||||
/// <param name="targetSummary">Optional summary of the operation target.</param>
|
||||
/// <param name="sourceInstanceId">Optional ID of the source instance.</param>
|
||||
/// <param name="sourceScript">Optional name of the source script.</param>
|
||||
/// <param name="sourceNode">Optional source node identifier.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task RecordEnqueueAsync(
|
||||
TrackedOperationId id,
|
||||
string kind,
|
||||
@@ -49,6 +56,12 @@ public interface IOperationTrackingStore
|
||||
/// already applied) are NOT mutated — the operation has reached its final
|
||||
/// outcome and any late-arriving attempt telemetry is dropped on the floor.
|
||||
/// </summary>
|
||||
/// <param name="id">Operation ID to update.</param>
|
||||
/// <param name="status">Current operation status.</param>
|
||||
/// <param name="retryCount">Number of retry attempts.</param>
|
||||
/// <param name="lastError">Optional error message from the last attempt.</param>
|
||||
/// <param name="httpStatus">Optional HTTP status code from the last attempt.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task RecordAttemptAsync(
|
||||
TrackedOperationId id,
|
||||
string status,
|
||||
@@ -62,6 +75,11 @@ public interface IOperationTrackingStore
|
||||
/// <c>TerminalAtUtc = now</c> and writes the final status / error. A row
|
||||
/// already in terminal state is left untouched (first-write-wins).
|
||||
/// </summary>
|
||||
/// <param name="id">Operation ID to mark as terminal.</param>
|
||||
/// <param name="status">Final operation status.</param>
|
||||
/// <param name="lastError">Optional final error message.</param>
|
||||
/// <param name="httpStatus">Optional final HTTP status code.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task RecordTerminalAsync(
|
||||
TrackedOperationId id,
|
||||
string status,
|
||||
@@ -73,6 +91,9 @@ public interface IOperationTrackingStore
|
||||
/// Return the latest snapshot for the supplied id, or <c>null</c> when no
|
||||
/// tracking row exists (purged or never recorded).
|
||||
/// </summary>
|
||||
/// <param name="id">Operation ID to fetch status for.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Tracking status snapshot, or null if not found.</returns>
|
||||
Task<TrackingStatusSnapshot?> GetStatusAsync(
|
||||
TrackedOperationId id,
|
||||
CancellationToken ct = default);
|
||||
@@ -82,6 +103,8 @@ public interface IOperationTrackingStore
|
||||
/// <paramref name="olderThanUtc"/>. Non-terminal rows are kept regardless
|
||||
/// of age (the operation is still in flight).
|
||||
/// </summary>
|
||||
/// <param name="olderThanUtc">Cutoff timestamp; rows terminal before this are deleted.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task PurgeTerminalAsync(
|
||||
DateTime olderThanUtc,
|
||||
CancellationToken ct = default);
|
||||
|
||||
@@ -36,6 +36,8 @@ public interface IPartitionMaintenance
|
||||
/// boundary that already exists is skipped rather than re-issued.
|
||||
/// Returns the boundaries actually added, in chronological order.
|
||||
/// </summary>
|
||||
/// <param name="lookaheadMonths">Number of future monthly boundaries to ensure exist.</param>
|
||||
/// <param name="ct">Cancellation token for the SQL operation.</param>
|
||||
Task<IReadOnlyList<DateTime>> EnsureLookaheadAsync(int lookaheadMonths, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -44,5 +46,6 @@ public interface IPartitionMaintenance
|
||||
/// Returns <c>null</c> when the partition function does not exist or
|
||||
/// has no boundaries.
|
||||
/// </summary>
|
||||
/// <param name="ct">Cancellation token for the SQL operation.</param>
|
||||
Task<DateTime?> GetMaxBoundaryAsync(CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -8,19 +8,62 @@ public record TagValue(object? Value, QualityCode Quality, DateTimeOffset Timest
|
||||
public record ReadResult(bool Success, TagValue? Value, string? ErrorMessage);
|
||||
public record WriteResult(bool Success, string? ErrorMessage);
|
||||
|
||||
/// <summary>Callback invoked when a subscribed tag value changes.</summary>
|
||||
/// <param name="tagPath">The tag path whose value has changed.</param>
|
||||
/// <param name="value">The new tag value including quality and timestamp.</param>
|
||||
public delegate void SubscriptionCallback(string tagPath, TagValue value);
|
||||
|
||||
public interface IDataConnection : IAsyncDisposable
|
||||
{
|
||||
/// <summary>Establishes the protocol connection using the provided connection details.</summary>
|
||||
/// <param name="connectionDetails">Protocol-specific key-value configuration pairs.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task ConnectAsync(IDictionary<string, string> connectionDetails, CancellationToken cancellationToken = default);
|
||||
/// <summary>Gracefully terminates the protocol connection.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DisconnectAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>Subscribes to value-change notifications for a tag path; returns a subscription ID.</summary>
|
||||
/// <param name="tagPath">The tag path to subscribe to.</param>
|
||||
/// <param name="callback">Callback invoked on each value change.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A subscription ID that can be passed to <see cref="UnsubscribeAsync"/>.</returns>
|
||||
Task<string> SubscribeAsync(string tagPath, SubscriptionCallback callback, CancellationToken cancellationToken = default);
|
||||
/// <summary>Cancels an active subscription by its ID.</summary>
|
||||
/// <param name="subscriptionId">The subscription ID returned by <see cref="SubscribeAsync"/>.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UnsubscribeAsync(string subscriptionId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Reads the current value of a single tag.</summary>
|
||||
/// <param name="tagPath">The tag path to read.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The read result containing the value or an error.</returns>
|
||||
Task<ReadResult> ReadAsync(string tagPath, CancellationToken cancellationToken = default);
|
||||
/// <summary>Reads the current values of multiple tags in a single round-trip.</summary>
|
||||
/// <param name="tagPaths">The tag paths to read.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A dictionary of tag paths to their read results.</returns>
|
||||
Task<IReadOnlyDictionary<string, ReadResult>> ReadBatchAsync(IEnumerable<string> tagPaths, CancellationToken cancellationToken = default);
|
||||
/// <summary>Writes a value to a single tag.</summary>
|
||||
/// <param name="tagPath">The tag path to write.</param>
|
||||
/// <param name="value">The value to write.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The write result indicating success or failure.</returns>
|
||||
Task<WriteResult> WriteAsync(string tagPath, object? value, CancellationToken cancellationToken = default);
|
||||
/// <summary>Writes values to multiple tags in a single round-trip.</summary>
|
||||
/// <param name="values">A dictionary of tag paths to values.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A dictionary of tag paths to their write results.</returns>
|
||||
Task<IReadOnlyDictionary<string, WriteResult>> WriteBatchAsync(IDictionary<string, object?> values, CancellationToken cancellationToken = default);
|
||||
/// <summary>Writes a batch of values, then writes a flag and waits for a specific response value within the timeout.</summary>
|
||||
/// <param name="values">Tag values to write before the flag.</param>
|
||||
/// <param name="flagPath">Tag path of the trigger flag.</param>
|
||||
/// <param name="flagValue">Value to write to the flag tag.</param>
|
||||
/// <param name="responsePath">Tag path to monitor for the expected response value.</param>
|
||||
/// <param name="responseValue">The response value that indicates completion.</param>
|
||||
/// <param name="timeout">Maximum time to wait for the response.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns><c>true</c> if the response value was observed within the timeout; otherwise <c>false</c>.</returns>
|
||||
Task<bool> WriteBatchAndWaitAsync(IDictionary<string, object?> values, string flagPath, object? flagValue, string responsePath, object? responseValue, TimeSpan timeout, CancellationToken cancellationToken = default);
|
||||
/// <summary>Current connection health status.</summary>
|
||||
ConnectionHealth Status { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -29,6 +29,8 @@ public interface IAuditLogRepository
|
||||
/// stored row untouched (first-write-wins). Bypasses the EF change tracker
|
||||
/// so the row never enters a tracked state.
|
||||
/// </summary>
|
||||
/// <param name="evt">The audit event to insert.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task InsertIfNotExistsAsync(AuditEvent evt, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -39,6 +41,9 @@ public interface IAuditLogRepository
|
||||
/// <see cref="AuditLogPaging.AfterOccurredAtUtc"/> +
|
||||
/// <see cref="AuditLogPaging.AfterEventId"/> to fetch the next page.
|
||||
/// </summary>
|
||||
/// <param name="filter">Filter criteria to apply to the query.</param>
|
||||
/// <param name="paging">Paging cursor and page size.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<IReadOnlyList<AuditEvent>> QueryAsync(
|
||||
AuditLogQueryFilter filter,
|
||||
AuditLogPaging paging,
|
||||
@@ -75,6 +80,8 @@ public interface IAuditLogRepository
|
||||
/// and the composite PK still rejects same-(EventId, OccurredAtUtc) rows.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="monthBoundary">Lower-bound datetime of the monthly partition to switch out.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<long> SwitchOutPartitionAsync(DateTime monthBoundary, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -85,6 +92,8 @@ public interface IAuditLogRepository
|
||||
/// excluded (a no-op switch is wasted work). Used by the M6 purge actor
|
||||
/// to enumerate retention-eligible months on every tick.
|
||||
/// </summary>
|
||||
/// <param name="threshold">Only partitions whose data is entirely older than this UTC datetime are returned.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<IReadOnlyList<DateTime>> GetPartitionBoundariesOlderThanAsync(
|
||||
DateTime threshold,
|
||||
CancellationToken ct = default);
|
||||
@@ -172,6 +181,8 @@ public interface IAuditLogRepository
|
||||
/// stub-node treatment of any other row-less execution.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="executionId">Any execution id in the chain; the implementation walks to the root and back down.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<IReadOnlyList<ExecutionTreeNode>> GetExecutionTreeAsync(
|
||||
Guid executionId,
|
||||
CancellationToken ct = default);
|
||||
@@ -182,5 +193,6 @@ public interface IAuditLogRepository
|
||||
/// "Node" multi-select filter dropdown — the Central UI caches the result
|
||||
/// for ~60s so the repository is hit at most once per minute per circuit.
|
||||
/// </summary>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<IReadOnlyList<string>> GetDistinctSourceNodesAsync(CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -8,15 +8,49 @@ namespace ScadaLink.Commons.Interfaces.Repositories;
|
||||
|
||||
public interface ICentralUiRepository
|
||||
{
|
||||
/// <summary>Returns all configured sites.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Site>> GetAllSitesAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>Returns all data connections for the specified site.</summary>
|
||||
/// <param name="siteId">The site database ID to filter by.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<DataConnection>> GetDataConnectionsBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Returns all data connections across all sites.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<DataConnection>> GetAllDataConnectionsAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>Returns the full template tree including folders and templates.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Template>> GetTemplateTreeAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>Returns instances filtered by optional site, template, or search term.</summary>
|
||||
/// <param name="siteId">Optional site ID to filter by.</param>
|
||||
/// <param name="templateId">Optional template ID to filter by.</param>
|
||||
/// <param name="searchTerm">Optional keyword to filter instance names.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Instance>> GetInstancesFilteredAsync(int? siteId = null, int? templateId = null, string? searchTerm = null, CancellationToken cancellationToken = default);
|
||||
/// <summary>Returns the most recent deployment records up to the specified count.</summary>
|
||||
/// <param name="count">Maximum number of records to return.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<DeploymentRecord>> GetRecentDeploymentsAsync(int count, CancellationToken cancellationToken = default);
|
||||
/// <summary>Returns the area tree for the specified site.</summary>
|
||||
/// <param name="siteId">The site database ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Area>> GetAreaTreeBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
|
||||
|
||||
// Audit log queries
|
||||
/// <summary>
|
||||
/// Queries audit log entries with optional filters, returning a page of results and the total matching count.
|
||||
/// </summary>
|
||||
/// <param name="user">Optional user filter.</param>
|
||||
/// <param name="entityType">Optional entity type filter.</param>
|
||||
/// <param name="action">Optional action filter.</param>
|
||||
/// <param name="from">Optional start of date range filter.</param>
|
||||
/// <param name="to">Optional end of date range filter.</param>
|
||||
/// <param name="entityId">Optional entity ID filter.</param>
|
||||
/// <param name="entityName">Optional entity name filter.</param>
|
||||
/// <param name="bundleImportId">Optional bundle import correlation ID filter.</param>
|
||||
/// <param name="page">One-based page number.</param>
|
||||
/// <param name="pageSize">Number of entries per page.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<(IReadOnlyList<AuditLogEntry> Entries, int TotalCount)> GetAuditLogEntriesAsync(
|
||||
string? user = null,
|
||||
string? entityType = null,
|
||||
@@ -30,5 +64,7 @@ public interface ICentralUiRepository
|
||||
int pageSize = 50,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Persists pending changes to the underlying store.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -7,31 +7,149 @@ namespace ScadaLink.Commons.Interfaces.Repositories;
|
||||
public interface IDeploymentManagerRepository
|
||||
{
|
||||
// DeploymentRecord
|
||||
/// <summary>
|
||||
/// Gets a deployment record by its ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The deployment record ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The deployment record, or null if not found.</returns>
|
||||
Task<DeploymentRecord?> GetDeploymentRecordByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Gets all deployment records.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A read-only list of all deployment records.</returns>
|
||||
Task<IReadOnlyList<DeploymentRecord>> GetAllDeploymentRecordsAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Gets all deployment records for a specific instance.
|
||||
/// </summary>
|
||||
/// <param name="instanceId">The instance ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A read-only list of deployment records for the instance.</returns>
|
||||
Task<IReadOnlyList<DeploymentRecord>> GetDeploymentsByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Gets the current deployment status for an instance.
|
||||
/// </summary>
|
||||
/// <param name="instanceId">The instance ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The current deployment record, or null if no deployment exists.</returns>
|
||||
Task<DeploymentRecord?> GetCurrentDeploymentStatusAsync(int instanceId, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Gets a deployment record by deployment ID.
|
||||
/// </summary>
|
||||
/// <param name="deploymentId">The deployment ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The deployment record, or null if not found.</returns>
|
||||
Task<DeploymentRecord?> GetDeploymentByDeploymentIdAsync(string deploymentId, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Adds a new deployment record.
|
||||
/// </summary>
|
||||
/// <param name="record">The deployment record to add.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task AddDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Updates an existing deployment record.
|
||||
/// </summary>
|
||||
/// <param name="record">The deployment record to update.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task UpdateDeploymentRecordAsync(DeploymentRecord record, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Deletes a deployment record by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The deployment record ID to delete.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task DeleteDeploymentRecordAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// SystemArtifactDeploymentRecord
|
||||
/// <summary>
|
||||
/// Gets a system artifact deployment record by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The system artifact deployment record ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The system artifact deployment record, or null if not found.</returns>
|
||||
Task<SystemArtifactDeploymentRecord?> GetSystemArtifactDeploymentByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Gets all system artifact deployment records.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A read-only list of all system artifact deployment records.</returns>
|
||||
Task<IReadOnlyList<SystemArtifactDeploymentRecord>> GetAllSystemArtifactDeploymentsAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Adds a new system artifact deployment record.
|
||||
/// </summary>
|
||||
/// <param name="record">The system artifact deployment record to add.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task AddSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Updates an existing system artifact deployment record.
|
||||
/// </summary>
|
||||
/// <param name="record">The system artifact deployment record to update.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task UpdateSystemArtifactDeploymentAsync(SystemArtifactDeploymentRecord record, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Deletes a system artifact deployment record by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The system artifact deployment record ID to delete.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task DeleteSystemArtifactDeploymentAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// WP-8: DeployedConfigSnapshot
|
||||
/// <summary>
|
||||
/// Gets the deployed config snapshot for an instance.
|
||||
/// </summary>
|
||||
/// <param name="instanceId">The instance ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The deployed config snapshot, or null if not found.</returns>
|
||||
Task<DeployedConfigSnapshot?> GetDeployedSnapshotByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Adds a new deployed config snapshot.
|
||||
/// </summary>
|
||||
/// <param name="snapshot">The deployed config snapshot to add.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task AddDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Updates an existing deployed config snapshot.
|
||||
/// </summary>
|
||||
/// <param name="snapshot">The deployed config snapshot to update.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task UpdateDeployedSnapshotAsync(DeployedConfigSnapshot snapshot, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Deletes the deployed config snapshot for an instance.
|
||||
/// </summary>
|
||||
/// <param name="instanceId">The instance ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task DeleteDeployedSnapshotAsync(int instanceId, CancellationToken cancellationToken = default);
|
||||
|
||||
// Instance lookups for deployment pipeline
|
||||
/// <summary>
|
||||
/// Gets an instance by ID.
|
||||
/// </summary>
|
||||
/// <param name="instanceId">The instance ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The instance, or null if not found.</returns>
|
||||
Task<Instance?> GetInstanceByIdAsync(int instanceId, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Gets an instance by unique name.
|
||||
/// </summary>
|
||||
/// <param name="uniqueName">The unique instance name.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The instance, or null if not found.</returns>
|
||||
Task<Instance?> GetInstanceByUniqueNameAsync(string uniqueName, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Updates an instance.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance to update.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task UpdateInstanceAsync(Instance instance, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -39,7 +157,15 @@ public interface IDeploymentManagerRepository
|
||||
/// records, deployed config snapshot, attribute/alarm overrides, and
|
||||
/// connection bindings.
|
||||
/// </summary>
|
||||
/// <param name="instanceId">The instance ID to delete.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task DeleteInstanceAsync(int instanceId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Saves all pending changes to the database.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the number of entities saved.</returns>
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@ namespace ScadaLink.Commons.Interfaces.Repositories;
|
||||
public interface IExternalSystemRepository
|
||||
{
|
||||
// ExternalSystemDefinition
|
||||
/// <summary>
|
||||
/// Gets an external system definition by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The external system ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The external system definition, or null if not found.</returns>
|
||||
Task<ExternalSystemDefinition?> GetExternalSystemByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -13,14 +19,46 @@ public interface IExternalSystemRepository
|
||||
/// <c>ExternalSystem.Call()</c>) does not have to fetch every system and filter
|
||||
/// in memory on each call (ExternalSystemGateway-011).
|
||||
/// </summary>
|
||||
/// <param name="name">The external system name.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The external system definition, or null if not found.</returns>
|
||||
Task<ExternalSystemDefinition?> GetExternalSystemByNameAsync(string name, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all external system definitions.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A read-only list of external system definitions.</returns>
|
||||
Task<IReadOnlyList<ExternalSystemDefinition>> GetAllExternalSystemsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new external system definition.
|
||||
/// </summary>
|
||||
/// <param name="definition">The external system definition to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddExternalSystemAsync(ExternalSystemDefinition definition, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing external system definition.
|
||||
/// </summary>
|
||||
/// <param name="definition">The external system definition to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateExternalSystemAsync(ExternalSystemDefinition definition, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an external system definition by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The external system ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteExternalSystemAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// ExternalSystemMethod
|
||||
/// <summary>
|
||||
/// Gets an external system method by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The method ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The external system method, or null if not found.</returns>
|
||||
Task<ExternalSystemMethod?> GetExternalSystemMethodByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -29,14 +67,48 @@ public interface IExternalSystemRepository
|
||||
/// resolution does not have to fetch every method of the system and filter in
|
||||
/// memory on each call (ExternalSystemGateway-011).
|
||||
/// </summary>
|
||||
/// <param name="externalSystemId">The external system ID.</param>
|
||||
/// <param name="methodName">The method name.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The external system method, or null if not found.</returns>
|
||||
Task<ExternalSystemMethod?> GetMethodByNameAsync(int externalSystemId, string methodName, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all methods for a given external system.
|
||||
/// </summary>
|
||||
/// <param name="externalSystemId">The external system ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A read-only list of external system methods.</returns>
|
||||
Task<IReadOnlyList<ExternalSystemMethod>> GetMethodsByExternalSystemIdAsync(int externalSystemId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new external system method.
|
||||
/// </summary>
|
||||
/// <param name="method">The external system method to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddExternalSystemMethodAsync(ExternalSystemMethod method, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing external system method.
|
||||
/// </summary>
|
||||
/// <param name="method">The external system method to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateExternalSystemMethodAsync(ExternalSystemMethod method, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an external system method by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The method ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteExternalSystemMethodAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// DatabaseConnectionDefinition
|
||||
/// <summary>
|
||||
/// Gets a database connection definition by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The database connection ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The database connection definition, or null if not found.</returns>
|
||||
Task<DatabaseConnectionDefinition?> GetDatabaseConnectionByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -46,12 +118,43 @@ public interface IExternalSystemRepository
|
||||
/// not have to fetch every connection and filter in memory on each call
|
||||
/// (ExternalSystemGateway-011).
|
||||
/// </summary>
|
||||
/// <param name="name">The database connection name.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The database connection definition, or null if not found.</returns>
|
||||
Task<DatabaseConnectionDefinition?> GetDatabaseConnectionByNameAsync(string name, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all database connection definitions.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A read-only list of database connection definitions.</returns>
|
||||
Task<IReadOnlyList<DatabaseConnectionDefinition>> GetAllDatabaseConnectionsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new database connection definition.
|
||||
/// </summary>
|
||||
/// <param name="definition">The database connection definition to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddDatabaseConnectionAsync(DatabaseConnectionDefinition definition, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing database connection definition.
|
||||
/// </summary>
|
||||
/// <param name="definition">The database connection definition to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateDatabaseConnectionAsync(DatabaseConnectionDefinition definition, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a database connection definition by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The database connection ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteDatabaseConnectionAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Saves pending changes to the repository.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The number of entities saved.</returns>
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -5,21 +5,60 @@ namespace ScadaLink.Commons.Interfaces.Repositories;
|
||||
public interface IInboundApiRepository
|
||||
{
|
||||
// ApiKey
|
||||
/// <summary>Retrieves an API key by ID.</summary>
|
||||
/// <param name="id">The API key ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<ApiKey?> GetApiKeyByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves all API keys.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<ApiKey>> GetAllApiKeysAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves an API key by value.</summary>
|
||||
/// <param name="keyValue">The API key value.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<ApiKey?> GetApiKeyByValueAsync(string keyValue, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new API key.</summary>
|
||||
/// <param name="apiKey">The API key to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddApiKeyAsync(ApiKey apiKey, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing API key.</summary>
|
||||
/// <param name="apiKey">The API key to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateApiKeyAsync(ApiKey apiKey, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes an API key by ID.</summary>
|
||||
/// <param name="id">The API key ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteApiKeyAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// ApiMethod
|
||||
/// <summary>Retrieves an API method by ID.</summary>
|
||||
/// <param name="id">The API method ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<ApiMethod?> GetApiMethodByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves all API methods.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<ApiMethod>> GetAllApiMethodsAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves an API method by name.</summary>
|
||||
/// <param name="name">The API method name.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<ApiMethod?> GetMethodByNameAsync(string name, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves API keys approved for a method.</summary>
|
||||
/// <param name="methodId">The API method ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<ApiKey>> GetApprovedKeysForMethodAsync(int methodId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new API method.</summary>
|
||||
/// <param name="method">The API method to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddApiMethodAsync(ApiMethod method, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing API method.</summary>
|
||||
/// <param name="method">The API method to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateApiMethodAsync(ApiMethod method, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes an API method by ID.</summary>
|
||||
/// <param name="id">The API method ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteApiMethodAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Saves pending changes to the database.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ public interface INotificationOutboxRepository
|
||||
/// row was inserted, <c>false</c> when an existing row was left untouched.
|
||||
/// Commits internally — this call is its own transaction.
|
||||
/// </summary>
|
||||
/// <param name="n">The notification to insert.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>True if inserted, false if already exists.</returns>
|
||||
Task<bool> InsertIfNotExistsAsync(Notification n, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -30,21 +33,35 @@ public interface INotificationOutboxRepository
|
||||
/// Terminal rows are excluded. Ordered by <c>CreatedAt</c> ascending, capped at
|
||||
/// <paramref name="batchSize"/>.
|
||||
/// </summary>
|
||||
/// <param name="now">The current time for evaluating due retries.</param>
|
||||
/// <param name="batchSize">Maximum number of rows to return.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A list of notifications ready for delivery.</returns>
|
||||
Task<IReadOnlyList<Notification>> GetDueAsync(DateTimeOffset now, int batchSize, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Returns the notification with the given id, or <c>null</c>.</summary>
|
||||
/// <param name="notificationId">The notification identifier.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The notification, or null if not found.</returns>
|
||||
Task<Notification?> GetByIdAsync(string notificationId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Marks <paramref name="n"/> modified and persists it (status transitions).
|
||||
/// Commits internally — this call is its own transaction.
|
||||
/// </summary>
|
||||
/// <param name="n">The notification to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateAsync(Notification n, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a page of notifications matching <paramref name="filter"/>, ordered by
|
||||
/// <c>CreatedAt</c> descending, together with the total matching count.
|
||||
/// </summary>
|
||||
/// <param name="filter">The query filter.</param>
|
||||
/// <param name="pageNumber">The page number (1-based).</param>
|
||||
/// <param name="pageSize">The page size.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A tuple of rows and total count.</returns>
|
||||
Task<(IReadOnlyList<Notification> Rows, int TotalCount)> QueryAsync(
|
||||
NotificationOutboxFilter filter, int pageNumber, int pageSize, CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -52,6 +69,9 @@ public interface INotificationOutboxRepository
|
||||
/// Bulk-deletes terminal rows (Delivered/Parked/Discarded) whose <c>CreatedAt</c> is
|
||||
/// older than <paramref name="cutoff"/>. Returns the number of rows deleted.
|
||||
/// </summary>
|
||||
/// <param name="cutoff">The cutoff time for deletion.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The number of rows deleted.</returns>
|
||||
Task<int> DeleteTerminalOlderThanAsync(DateTimeOffset cutoff, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -59,6 +79,10 @@ public interface INotificationOutboxRepository
|
||||
/// delivered cutoffs are supplied by the caller; the current time used for
|
||||
/// <c>OldestPendingAge</c> is captured inside the method.
|
||||
/// </summary>
|
||||
/// <param name="stuckCutoff">The time threshold for marking notifications as stuck.</param>
|
||||
/// <param name="deliveredSince">The time threshold for counting delivered notifications.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A KPI snapshot.</returns>
|
||||
Task<NotificationKpiSnapshot> ComputeKpisAsync(
|
||||
DateTimeOffset stuckCutoff, DateTimeOffset deliveredSince, CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -68,6 +92,10 @@ public interface INotificationOutboxRepository
|
||||
/// are supplied by the caller; the current time used for <c>OldestPendingAge</c> is
|
||||
/// captured inside the method.
|
||||
/// </summary>
|
||||
/// <param name="stuckCutoff">The time threshold for marking notifications as stuck.</param>
|
||||
/// <param name="deliveredSince">The time threshold for counting delivered notifications.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A list of per-site KPI snapshots.</returns>
|
||||
Task<IReadOnlyList<SiteNotificationKpiSnapshot>> ComputePerSiteKpisAsync(
|
||||
DateTimeOffset stuckCutoff, DateTimeOffset deliveredSince, CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -76,5 +104,7 @@ public interface INotificationOutboxRepository
|
||||
/// multiple changes for a single commit; the individual mutating methods on this
|
||||
/// interface already commit on their own.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The number of changes persisted.</returns>
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -5,26 +5,95 @@ namespace ScadaLink.Commons.Interfaces.Repositories;
|
||||
public interface INotificationRepository
|
||||
{
|
||||
// NotificationList
|
||||
/// <summary>Gets a notification list by ID.</summary>
|
||||
/// <param name="id">The notification list ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The notification list, or null if not found.</returns>
|
||||
Task<NotificationList?> GetNotificationListByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Gets all notification lists.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A read-only list of notification lists.</returns>
|
||||
Task<IReadOnlyList<NotificationList>> GetAllNotificationListsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Gets a notification list by name.</summary>
|
||||
/// <param name="name">The notification list name.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The notification list, or null if not found.</returns>
|
||||
Task<NotificationList?> GetListByNameAsync(string name, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Adds a new notification list.</summary>
|
||||
/// <param name="list">The notification list to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddNotificationListAsync(NotificationList list, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Updates an existing notification list.</summary>
|
||||
/// <param name="list">The notification list to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateNotificationListAsync(NotificationList list, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Deletes a notification list by ID.</summary>
|
||||
/// <param name="id">The notification list ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteNotificationListAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// NotificationRecipient
|
||||
/// <summary>Gets a notification recipient by ID.</summary>
|
||||
/// <param name="id">The recipient ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The notification recipient, or null if not found.</returns>
|
||||
Task<NotificationRecipient?> GetRecipientByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Gets all recipients in a notification list.</summary>
|
||||
/// <param name="notificationListId">The notification list ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A read-only list of recipients.</returns>
|
||||
Task<IReadOnlyList<NotificationRecipient>> GetRecipientsByListIdAsync(int notificationListId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Adds a new notification recipient.</summary>
|
||||
/// <param name="recipient">The recipient to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddRecipientAsync(NotificationRecipient recipient, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Updates an existing notification recipient.</summary>
|
||||
/// <param name="recipient">The recipient to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateRecipientAsync(NotificationRecipient recipient, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Deletes a notification recipient by ID.</summary>
|
||||
/// <param name="id">The recipient ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteRecipientAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// SmtpConfiguration
|
||||
/// <summary>Gets an SMTP configuration by ID.</summary>
|
||||
/// <param name="id">The SMTP configuration ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The SMTP configuration, or null if not found.</returns>
|
||||
Task<SmtpConfiguration?> GetSmtpConfigurationByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Gets all SMTP configurations.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A read-only list of SMTP configurations.</returns>
|
||||
Task<IReadOnlyList<SmtpConfiguration>> GetAllSmtpConfigurationsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Adds a new SMTP configuration.</summary>
|
||||
/// <param name="configuration">The SMTP configuration to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddSmtpConfigurationAsync(SmtpConfiguration configuration, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Updates an existing SMTP configuration.</summary>
|
||||
/// <param name="configuration">The SMTP configuration to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateSmtpConfigurationAsync(SmtpConfiguration configuration, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Deletes an SMTP configuration by ID.</summary>
|
||||
/// <param name="id">The SMTP configuration ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteSmtpConfigurationAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Saves pending changes to the repository.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The number of entities saved.</returns>
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -5,19 +5,89 @@ namespace ScadaLink.Commons.Interfaces.Repositories;
|
||||
public interface ISecurityRepository
|
||||
{
|
||||
// LdapGroupMapping
|
||||
/// <summary>
|
||||
/// Gets an LDAP group mapping by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The mapping ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The LDAP group mapping, or null if not found.</returns>
|
||||
Task<LdapGroupMapping?> GetMappingByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Gets all LDAP group mappings.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A read-only list of all LDAP group mappings.</returns>
|
||||
Task<IReadOnlyList<LdapGroupMapping>> GetAllMappingsAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Gets all LDAP group mappings for a specific role.
|
||||
/// </summary>
|
||||
/// <param name="role">The role name.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A read-only list of LDAP group mappings for the role.</returns>
|
||||
Task<IReadOnlyList<LdapGroupMapping>> GetMappingsByRoleAsync(string role, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Adds a new LDAP group mapping.
|
||||
/// </summary>
|
||||
/// <param name="mapping">The LDAP group mapping to add.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task AddMappingAsync(LdapGroupMapping mapping, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Updates an existing LDAP group mapping.
|
||||
/// </summary>
|
||||
/// <param name="mapping">The LDAP group mapping to update.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task UpdateMappingAsync(LdapGroupMapping mapping, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Deletes an LDAP group mapping by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The mapping ID to delete.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task DeleteMappingAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// SiteScopeRule
|
||||
/// <summary>
|
||||
/// Gets a site scope rule by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The scope rule ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>The site scope rule, or null if not found.</returns>
|
||||
Task<SiteScopeRule?> GetScopeRuleByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Gets all site scope rules for an LDAP group mapping.
|
||||
/// </summary>
|
||||
/// <param name="ldapGroupMappingId">The LDAP group mapping ID.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A read-only list of scope rules for the mapping.</returns>
|
||||
Task<IReadOnlyList<SiteScopeRule>> GetScopeRulesForMappingAsync(int ldapGroupMappingId, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Adds a new site scope rule.
|
||||
/// </summary>
|
||||
/// <param name="rule">The site scope rule to add.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task AddScopeRuleAsync(SiteScopeRule rule, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Updates an existing site scope rule.
|
||||
/// </summary>
|
||||
/// <param name="rule">The site scope rule to update.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task UpdateScopeRuleAsync(SiteScopeRule rule, CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Deletes a site scope rule by ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The scope rule ID to delete.</param>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task DeleteScopeRuleAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Saves all pending changes to the database.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||
/// <returns>A task representing the number of entities saved.</returns>
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -37,11 +37,15 @@ public interface ISiteCallAuditRepository
|
||||
/// the stored status' rank. Out-of-order / duplicate updates are silently
|
||||
/// dropped (monotonic forward-only progression).
|
||||
/// </summary>
|
||||
/// <param name="siteCall">The site call row to insert or monotonically update.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task UpsertAsync(SiteCall siteCall, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the row for the given id, or <c>null</c> if none exists.
|
||||
/// </summary>
|
||||
/// <param name="id">The tracked operation id to look up.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<SiteCall?> GetAsync(TrackedOperationId id, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -51,6 +55,9 @@ public interface ISiteCallAuditRepository
|
||||
/// <see cref="SiteCallPaging.AfterCreatedAtUtc"/> + <see cref="SiteCallPaging.AfterId"/>
|
||||
/// to fetch subsequent pages.
|
||||
/// </summary>
|
||||
/// <param name="filter">Filter criteria for the query.</param>
|
||||
/// <param name="paging">Keyset paging parameters.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<IReadOnlyList<SiteCall>> QueryAsync(
|
||||
SiteCallQueryFilter filter,
|
||||
SiteCallPaging paging,
|
||||
@@ -62,6 +69,8 @@ public interface ISiteCallAuditRepository
|
||||
/// (TerminalAtUtc IS NULL) are NEVER purged. Returns the number of rows
|
||||
/// deleted.
|
||||
/// </summary>
|
||||
/// <param name="olderThanUtc">UTC cutoff; terminal rows older than this are deleted.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<int> PurgeTerminalAsync(DateTime olderThanUtc, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -72,6 +81,9 @@ public interface ISiteCallAuditRepository
|
||||
/// <paramref name="intervalSince"/>; the current time for <c>OldestPendingAge</c>
|
||||
/// is captured inside the method.
|
||||
/// </summary>
|
||||
/// <param name="stuckCutoff">UTC threshold for classifying a row as stuck.</param>
|
||||
/// <param name="intervalSince">UTC start of the delivered/failed interval window.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<SiteCallKpiSnapshot> ComputeKpisAsync(
|
||||
DateTime stuckCutoff,
|
||||
DateTime intervalSince,
|
||||
@@ -82,6 +94,9 @@ public interface ISiteCallAuditRepository
|
||||
/// site. Sites with no <c>SiteCalls</c> rows at all are omitted. The stuck
|
||||
/// cutoff and interval bounds are interpreted as in <see cref="ComputeKpisAsync"/>.
|
||||
/// </summary>
|
||||
/// <param name="stuckCutoff">UTC threshold for classifying a row as stuck.</param>
|
||||
/// <param name="intervalSince">UTC start of the delivered/failed interval window.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<IReadOnlyList<SiteCallSiteKpiSnapshot>> ComputePerSiteKpisAsync(
|
||||
DateTime stuckCutoff,
|
||||
DateTime intervalSince,
|
||||
|
||||
@@ -9,23 +9,62 @@ namespace ScadaLink.Commons.Interfaces.Repositories;
|
||||
public interface ISiteRepository
|
||||
{
|
||||
// Sites
|
||||
/// <summary>Retrieves a site by its ID.</summary>
|
||||
/// <param name="id">The site primary key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<Site?> GetSiteByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves a site by its identifier.</summary>
|
||||
/// <param name="siteIdentifier">The unique site identifier string.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<Site?> GetSiteByIdentifierAsync(string siteIdentifier, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves all sites.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Site>> GetAllSitesAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new site.</summary>
|
||||
/// <param name="site">The site entity to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddSiteAsync(Site site, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing site.</summary>
|
||||
/// <param name="site">The site entity with updated values.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateSiteAsync(Site site, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes a site.</summary>
|
||||
/// <param name="id">The site primary key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteSiteAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// Data Connections
|
||||
/// <summary>Retrieves a data connection by its ID.</summary>
|
||||
/// <param name="id">The data connection primary key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<DataConnection?> GetDataConnectionByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves all data connections.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<DataConnection>> GetAllDataConnectionsAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves all data connections for a site.</summary>
|
||||
/// <param name="siteId">The site primary key to filter by.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<DataConnection>> GetDataConnectionsBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new data connection.</summary>
|
||||
/// <param name="connection">The data connection entity to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddDataConnectionAsync(DataConnection connection, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing data connection.</summary>
|
||||
/// <param name="connection">The data connection entity with updated values.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateDataConnectionAsync(DataConnection connection, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes a data connection.</summary>
|
||||
/// <param name="id">The data connection primary key.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteDataConnectionAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// Instances (for deletion constraint checks)
|
||||
/// <summary>Retrieves all instances deployed to a site.</summary>
|
||||
/// <param name="siteId">The site primary key to filter by.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Instance>> GetInstancesBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Saves all pending changes to the database.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,16 @@ namespace ScadaLink.Commons.Interfaces.Repositories;
|
||||
public interface ITemplateEngineRepository
|
||||
{
|
||||
// Template
|
||||
/// <summary>Retrieves a template by ID.</summary>
|
||||
/// <param name="id">The template ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<Template?> GetTemplateByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves a template with its child entities by ID.</summary>
|
||||
/// <param name="id">The template ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<Template?> GetTemplateWithChildrenAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves all templates.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Template>> GetAllTemplatesAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>
|
||||
/// Returns every template that contains a composition referencing
|
||||
@@ -16,89 +24,271 @@ public interface ITemplateEngineRepository
|
||||
/// its Attributes / Scripts / Compositions so the caller can build a
|
||||
/// CompositionContext without a follow-up round-trip per parent.
|
||||
/// </summary>
|
||||
/// <param name="composedTemplateId">The composed template ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Template>> GetTemplatesComposingAsync(int composedTemplateId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new template.</summary>
|
||||
/// <param name="template">The template to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddTemplateAsync(Template template, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing template.</summary>
|
||||
/// <param name="template">The template to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateTemplateAsync(Template template, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes a template by ID.</summary>
|
||||
/// <param name="id">The template ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteTemplateAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// TemplateAttribute
|
||||
/// <summary>Retrieves a template attribute by ID.</summary>
|
||||
/// <param name="id">The attribute ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<TemplateAttribute?> GetTemplateAttributeByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves attributes for a template.</summary>
|
||||
/// <param name="templateId">The template ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<TemplateAttribute>> GetAttributesByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new template attribute.</summary>
|
||||
/// <param name="attribute">The attribute to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddTemplateAttributeAsync(TemplateAttribute attribute, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing template attribute.</summary>
|
||||
/// <param name="attribute">The attribute to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateTemplateAttributeAsync(TemplateAttribute attribute, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes a template attribute by ID.</summary>
|
||||
/// <param name="id">The attribute ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteTemplateAttributeAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// TemplateAlarm
|
||||
/// <summary>Retrieves a template alarm by ID.</summary>
|
||||
/// <param name="id">The alarm ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<TemplateAlarm?> GetTemplateAlarmByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves alarms for a template.</summary>
|
||||
/// <param name="templateId">The template ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<TemplateAlarm>> GetAlarmsByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new template alarm.</summary>
|
||||
/// <param name="alarm">The alarm to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddTemplateAlarmAsync(TemplateAlarm alarm, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing template alarm.</summary>
|
||||
/// <param name="alarm">The alarm to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateTemplateAlarmAsync(TemplateAlarm alarm, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes a template alarm by ID.</summary>
|
||||
/// <param name="id">The alarm ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteTemplateAlarmAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// TemplateScript
|
||||
/// <summary>Retrieves a template script by ID.</summary>
|
||||
/// <param name="id">The script ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<TemplateScript?> GetTemplateScriptByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves scripts for a template.</summary>
|
||||
/// <param name="templateId">The template ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<TemplateScript>> GetScriptsByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new template script.</summary>
|
||||
/// <param name="script">The script to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddTemplateScriptAsync(TemplateScript script, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing template script.</summary>
|
||||
/// <param name="script">The script to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateTemplateScriptAsync(TemplateScript script, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes a template script by ID.</summary>
|
||||
/// <param name="id">The script ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteTemplateScriptAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// TemplateComposition
|
||||
/// <summary>Retrieves a template composition by ID.</summary>
|
||||
/// <param name="id">The composition ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<TemplateComposition?> GetTemplateCompositionByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves compositions for a template.</summary>
|
||||
/// <param name="templateId">The template ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<TemplateComposition>> GetCompositionsByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new template composition.</summary>
|
||||
/// <param name="composition">The composition to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddTemplateCompositionAsync(TemplateComposition composition, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing template composition.</summary>
|
||||
/// <param name="composition">The composition to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateTemplateCompositionAsync(TemplateComposition composition, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes a template composition by ID.</summary>
|
||||
/// <param name="id">The composition ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteTemplateCompositionAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// Instance
|
||||
/// <summary>Retrieves an instance by ID.</summary>
|
||||
/// <param name="id">The instance ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<Instance?> GetInstanceByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves all instances.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Instance>> GetAllInstancesAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves instances for a template.</summary>
|
||||
/// <param name="templateId">The template ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Instance>> GetInstancesByTemplateIdAsync(int templateId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves instances for a site.</summary>
|
||||
/// <param name="siteId">The site ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Instance>> GetInstancesBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves an instance by unique name.</summary>
|
||||
/// <param name="uniqueName">The unique instance name.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<Instance?> GetInstanceByUniqueNameAsync(string uniqueName, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new instance.</summary>
|
||||
/// <param name="instance">The instance to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddInstanceAsync(Instance instance, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing instance.</summary>
|
||||
/// <param name="instance">The instance to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateInstanceAsync(Instance instance, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes an instance by ID.</summary>
|
||||
/// <param name="id">The instance ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteInstanceAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// InstanceAttributeOverride
|
||||
/// <summary>Retrieves attribute overrides for an instance.</summary>
|
||||
/// <param name="instanceId">The instance ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<InstanceAttributeOverride>> GetOverridesByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new instance attribute override.</summary>
|
||||
/// <param name="attributeOverride">The override to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddInstanceAttributeOverrideAsync(InstanceAttributeOverride attributeOverride, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing instance attribute override.</summary>
|
||||
/// <param name="attributeOverride">The override to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateInstanceAttributeOverrideAsync(InstanceAttributeOverride attributeOverride, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes an instance attribute override by ID.</summary>
|
||||
/// <param name="id">The override ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteInstanceAttributeOverrideAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// InstanceAlarmOverride
|
||||
/// <summary>Retrieves alarm overrides for an instance.</summary>
|
||||
/// <param name="instanceId">The instance ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<InstanceAlarmOverride>> GetAlarmOverridesByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves an alarm override by instance and alarm name.</summary>
|
||||
/// <param name="instanceId">The instance ID.</param>
|
||||
/// <param name="alarmCanonicalName">The alarm canonical name.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<InstanceAlarmOverride?> GetAlarmOverrideAsync(int instanceId, string alarmCanonicalName, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new instance alarm override.</summary>
|
||||
/// <param name="alarmOverride">The override to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddInstanceAlarmOverrideAsync(InstanceAlarmOverride alarmOverride, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing instance alarm override.</summary>
|
||||
/// <param name="alarmOverride">The override to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateInstanceAlarmOverrideAsync(InstanceAlarmOverride alarmOverride, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes an instance alarm override by ID.</summary>
|
||||
/// <param name="id">The override ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteInstanceAlarmOverrideAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// InstanceConnectionBinding
|
||||
/// <summary>Retrieves connection bindings for an instance.</summary>
|
||||
/// <param name="instanceId">The instance ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<InstanceConnectionBinding>> GetBindingsByInstanceIdAsync(int instanceId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new instance connection binding.</summary>
|
||||
/// <param name="binding">The binding to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddInstanceConnectionBindingAsync(InstanceConnectionBinding binding, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing instance connection binding.</summary>
|
||||
/// <param name="binding">The binding to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateInstanceConnectionBindingAsync(InstanceConnectionBinding binding, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes an instance connection binding by ID.</summary>
|
||||
/// <param name="id">The binding ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteInstanceConnectionBindingAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// Area
|
||||
/// <summary>Retrieves an area by ID.</summary>
|
||||
/// <param name="id">The area ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<Area?> GetAreaByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves areas for a site.</summary>
|
||||
/// <param name="siteId">The site ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<Area>> GetAreasBySiteIdAsync(int siteId, CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new area.</summary>
|
||||
/// <param name="area">The area to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddAreaAsync(Area area, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing area.</summary>
|
||||
/// <param name="area">The area to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateAreaAsync(Area area, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes an area by ID.</summary>
|
||||
/// <param name="id">The area ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteAreaAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// SharedScript
|
||||
/// <summary>Retrieves a shared script by ID.</summary>
|
||||
/// <param name="id">The script ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<SharedScript?> GetSharedScriptByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves a shared script by name.</summary>
|
||||
/// <param name="name">The script name.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<SharedScript?> GetSharedScriptByNameAsync(string name, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves all shared scripts.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<SharedScript>> GetAllSharedScriptsAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new shared script.</summary>
|
||||
/// <param name="sharedScript">The script to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddSharedScriptAsync(SharedScript sharedScript, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing shared script.</summary>
|
||||
/// <param name="sharedScript">The script to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateSharedScriptAsync(SharedScript sharedScript, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes a shared script by ID.</summary>
|
||||
/// <param name="id">The script ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteSharedScriptAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
// TemplateFolder
|
||||
/// <summary>Retrieves a template folder by ID.</summary>
|
||||
/// <param name="id">The folder ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<TemplateFolder?> GetFolderByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
/// <summary>Retrieves all template folders.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<IReadOnlyList<TemplateFolder>> GetAllFoldersAsync(CancellationToken cancellationToken = default);
|
||||
/// <summary>Adds a new template folder.</summary>
|
||||
/// <param name="folder">The folder to add.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task AddFolderAsync(TemplateFolder folder, CancellationToken cancellationToken = default);
|
||||
/// <summary>Updates an existing template folder.</summary>
|
||||
/// <param name="folder">The folder to update.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task UpdateFolderAsync(TemplateFolder folder, CancellationToken cancellationToken = default);
|
||||
/// <summary>Deletes a template folder by ID.</summary>
|
||||
/// <param name="id">The folder ID.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task DeleteFolderAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Saves pending changes to the database.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -2,5 +2,15 @@ namespace ScadaLink.Commons.Interfaces.Services;
|
||||
|
||||
public interface IAuditService
|
||||
{
|
||||
/// <summary>
|
||||
/// Appends an audit log entry recording a user action on an entity.
|
||||
/// </summary>
|
||||
/// <param name="user">The authenticated username performing the action.</param>
|
||||
/// <param name="action">The action performed (e.g., "Create", "Update", "Delete").</param>
|
||||
/// <param name="entityType">The type name of the affected entity.</param>
|
||||
/// <param name="entityId">The string representation of the entity's primary key.</param>
|
||||
/// <param name="entityName">The display name of the affected entity.</param>
|
||||
/// <param name="afterState">The entity state after the action; may be null for deletes.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the log write.</param>
|
||||
Task LogAsync(string user, string action, string entityType, string entityId, string entityName, object? afterState, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -13,5 +13,7 @@ public interface IAuditWriter
|
||||
/// Persist an audit event. Best-effort: implementations must swallow/log internal failures
|
||||
/// rather than propagating them to the calling boundary code.
|
||||
/// </summary>
|
||||
/// <param name="evt">The audit event to persist.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task WriteAsync(AuditEvent evt, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ public interface ICachedCallLifecycleObserver
|
||||
/// the per-category channel discriminator, retry-count + last-error
|
||||
/// context, and whether the outcome reached a terminal state.
|
||||
/// </summary>
|
||||
/// <param name="context">Per-attempt context including the tracking id, outcome, and audit provenance fields.</param>
|
||||
/// <param name="ct">Cancellation token for the observation operation.</param>
|
||||
Task OnAttemptCompletedAsync(CachedCallAttemptContext context, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,5 +30,7 @@ public interface ICachedCallTelemetryForwarder
|
||||
/// swallowed; the returned Task completes when both halves have been
|
||||
/// attempted.
|
||||
/// </summary>
|
||||
/// <param name="telemetry">The combined-telemetry packet to fan out.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task ForwardAsync(CachedCallTelemetry telemetry, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -12,5 +12,7 @@ public interface ICentralAuditWriter
|
||||
/// Persist an audit event into the central AuditLog table directly (bypassing site telemetry).
|
||||
/// Best-effort: implementations must swallow/log internal failures rather than propagating them.
|
||||
/// </summary>
|
||||
/// <param name="evt">The audit event to persist.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task WriteAsync(AuditEvent evt, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ public interface IDatabaseGateway
|
||||
/// Connection pooling is managed by the underlying provider.
|
||||
/// Caller is responsible for disposing.
|
||||
/// </summary>
|
||||
/// <param name="connectionName">Name of the configured database connection to open.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the async open operation.</param>
|
||||
Task<DbConnection> GetConnectionAsync(
|
||||
string connectionName,
|
||||
CancellationToken cancellationToken = default);
|
||||
@@ -48,6 +50,11 @@ public interface IDatabaseGateway
|
||||
/// retry-loop cached-write audit rows carry it. <c>null</c> for a
|
||||
/// non-routed run.
|
||||
/// </param>
|
||||
/// <param name="connectionName">Name of the configured database connection to write to.</param>
|
||||
/// <param name="sql">SQL statement to execute as a store-and-forward write.</param>
|
||||
/// <param name="parameters">Optional SQL parameters for the statement.</param>
|
||||
/// <param name="originInstanceName">Optional name of the instance that originated the write.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the buffering operation.</param>
|
||||
Task CachedWriteAsync(
|
||||
string connectionName,
|
||||
string sql,
|
||||
|
||||
@@ -11,6 +11,11 @@ public interface IExternalSystemClient
|
||||
/// <summary>
|
||||
/// Synchronous call to an external system. All failures returned to caller.
|
||||
/// </summary>
|
||||
/// <param name="systemName">The name of the external system.</param>
|
||||
/// <param name="methodName">The name of the method to invoke.</param>
|
||||
/// <param name="parameters">Method parameters as a dictionary, or null if none.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The result of the external call.</returns>
|
||||
Task<ExternalCallResult> CallAsync(
|
||||
string systemName,
|
||||
string methodName,
|
||||
@@ -21,6 +26,11 @@ public interface IExternalSystemClient
|
||||
/// Attempt immediate delivery; on transient failure, hand to S&F engine.
|
||||
/// Permanent failures returned to caller.
|
||||
/// </summary>
|
||||
/// <param name="systemName">The name of the external system.</param>
|
||||
/// <param name="methodName">The name of the method to invoke.</param>
|
||||
/// <param name="parameters">Method parameters as a dictionary, or null if none.</param>
|
||||
/// <param name="originInstanceName">The instance name originating the call, or null.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <param name="trackedOperationId">
|
||||
/// Audit Log #23 (M3): caller-supplied tracking id used as the
|
||||
/// store-and-forward message id so the S&F retry loop can read it
|
||||
@@ -49,6 +59,7 @@ public interface IExternalSystemClient
|
||||
/// retry-loop cached-call audit rows carry it. <c>null</c> for a non-routed
|
||||
/// run.
|
||||
/// </param>
|
||||
/// <returns>The result of the external call.</returns>
|
||||
Task<ExternalCallResult> CachedCallAsync(
|
||||
string systemName,
|
||||
string methodName,
|
||||
|
||||
@@ -10,6 +10,8 @@ public interface IInstanceLocator
|
||||
/// Resolves the site identifier for a given instance unique name.
|
||||
/// Returns null if the instance is not found.
|
||||
/// </summary>
|
||||
/// <param name="instanceUniqueName">System-wide unique name of the instance to look up.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<string?> GetSiteIdForInstanceAsync(
|
||||
string instanceUniqueName,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
@@ -10,6 +10,11 @@ public interface INotificationDeliveryService
|
||||
/// Sends a notification to a named list. Transient failures go to S&F.
|
||||
/// Permanent failures returned to caller.
|
||||
/// </summary>
|
||||
/// <param name="listName">Name of the notification list to deliver to.</param>
|
||||
/// <param name="subject">Subject line of the notification.</param>
|
||||
/// <param name="message">Plain-text body of the notification.</param>
|
||||
/// <param name="originInstanceName">Optional name of the instance that triggered the send.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the async operation.</param>
|
||||
Task<NotificationResult> SendAsync(
|
||||
string listName,
|
||||
string subject,
|
||||
|
||||
@@ -33,6 +33,8 @@ public interface ISiteAuditQueue
|
||||
/// oldest first. Idempotent — repeated calls before
|
||||
/// <see cref="MarkForwardedAsync"/> will yield the same rows again.
|
||||
/// </summary>
|
||||
/// <param name="limit">Maximum number of rows to return.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<IReadOnlyList<AuditEvent>> ReadPendingAsync(int limit, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -41,6 +43,8 @@ public interface ISiteAuditQueue
|
||||
/// <see cref="ScadaLink.Commons.Types.Enums.AuditForwardState.Forwarded"/>.
|
||||
/// Non-existent or already-forwarded ids are silent no-ops.
|
||||
/// </summary>
|
||||
/// <param name="eventIds">Event IDs to mark as forwarded.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task MarkForwardedAsync(IReadOnlyList<Guid> eventIds, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -58,6 +62,9 @@ public interface ISiteAuditQueue
|
||||
/// is oldest <see cref="AuditEvent.OccurredAtUtc"/> first with
|
||||
/// <see cref="AuditEvent.EventId"/> as the deterministic tiebreaker.
|
||||
/// </remarks>
|
||||
/// <param name="sinceUtc">Lower bound timestamp (UTC).</param>
|
||||
/// <param name="batchSize">Maximum number of rows to return.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<IReadOnlyList<AuditEvent>> ReadPendingSinceAsync(
|
||||
DateTime sinceUtc, int batchSize, CancellationToken ct = default);
|
||||
|
||||
@@ -70,6 +77,8 @@ public interface ISiteAuditQueue
|
||||
/// Rows already in <see cref="ScadaLink.Commons.Types.Enums.AuditForwardState.Reconciled"/>
|
||||
/// are left untouched (idempotent re-call). Non-existent ids are silent no-ops.
|
||||
/// </summary>
|
||||
/// <param name="eventIds">Event IDs to mark as reconciled.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task MarkReconciledAsync(IReadOnlyList<Guid> eventIds, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
@@ -83,5 +92,6 @@ public interface ISiteAuditQueue
|
||||
/// implementations are expected to take the same connection lock used by
|
||||
/// the hot-path INSERT batch and the drain queries.
|
||||
/// </summary>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<SiteAuditBacklogSnapshot> GetBacklogStatsAsync(CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -18,5 +18,6 @@ namespace ScadaLink.Commons.Interfaces.Transport;
|
||||
/// </summary>
|
||||
public interface IAuditCorrelationContext
|
||||
{
|
||||
/// <summary>Gets or sets the bundle import id used to correlate audit rows written during a bundle apply operation.</summary>
|
||||
Guid? BundleImportId { get; set; }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,14 @@ namespace ScadaLink.Commons.Interfaces.Transport;
|
||||
|
||||
public interface IBundleExporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports the selected artifacts as an encrypted or plain bundle stream.
|
||||
/// </summary>
|
||||
/// <param name="selection">Specifies which artifact types and ids to include in the bundle.</param>
|
||||
/// <param name="user">Username of the operator performing the export, stamped in the manifest.</param>
|
||||
/// <param name="sourceEnvironment">Environment label stamped in the bundle manifest.</param>
|
||||
/// <param name="passphrase">Optional passphrase to encrypt the bundle; null produces an unencrypted bundle.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
Task<Stream> ExportAsync(
|
||||
ExportSelection selection,
|
||||
string user,
|
||||
|
||||
@@ -4,8 +4,28 @@ namespace ScadaLink.Commons.Interfaces.Transport;
|
||||
|
||||
public interface IBundleImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates and decrypts the bundle stream, opens a session, and returns session metadata.
|
||||
/// </summary>
|
||||
/// <param name="bundleStream">Stream containing the bundle zip archive.</param>
|
||||
/// <param name="passphrase">Optional passphrase for decrypting an encrypted bundle.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<BundleSession> LoadAsync(Stream bundleStream, string? passphrase, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Diffs the loaded bundle against the target database and returns a per-artifact preview.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id returned by <see cref="LoadAsync"/>.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<ImportPreview> PreviewAsync(Guid sessionId, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Applies the chosen conflict resolutions and commits the import transaction.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">Session id returned by <see cref="LoadAsync"/>.</param>
|
||||
/// <param name="resolutions">Per-artifact conflict resolutions from the preview step.</param>
|
||||
/// <param name="user">Username of the operator performing the import, stamped in audit rows.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task<ImportResult> ApplyAsync(
|
||||
Guid sessionId,
|
||||
IReadOnlyList<ImportResolution> resolutions,
|
||||
|
||||
@@ -4,8 +4,15 @@ namespace ScadaLink.Commons.Interfaces.Transport;
|
||||
|
||||
public interface IBundleSessionStore
|
||||
{
|
||||
/// <summary>Stores the session and returns it; overwrites any existing session with the same id.</summary>
|
||||
/// <param name="session">The session to store.</param>
|
||||
BundleSession Open(BundleSession session);
|
||||
/// <summary>Returns the session for the given id, or null if not found or expired.</summary>
|
||||
/// <param name="sessionId">The session identifier to look up.</param>
|
||||
BundleSession? Get(Guid sessionId);
|
||||
/// <summary>Removes the session for the given id, if present.</summary>
|
||||
/// <param name="sessionId">The session identifier to remove.</param>
|
||||
void Remove(Guid sessionId);
|
||||
/// <summary>Removes all sessions whose expiry has passed.</summary>
|
||||
void EvictExpired();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ public static class ManagementCommandRegistry
|
||||
private static readonly FrozenDictionary<Type, string> NamesByType =
|
||||
Commands.ToFrozenDictionary(kv => kv.Value, kv => kv.Key);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a management command wire name to its CLR type, or null if not registered.
|
||||
/// </summary>
|
||||
/// <param name="commandName">The wire name of the management command (without the "Command" suffix).</param>
|
||||
public static Type? Resolve(string commandName)
|
||||
{
|
||||
return Commands.GetValueOrDefault(commandName);
|
||||
@@ -33,6 +37,7 @@ public static class ManagementCommandRegistry
|
||||
/// <summary>
|
||||
/// Returns the registered wire name for a management command type.
|
||||
/// </summary>
|
||||
/// <param name="commandType">The CLR type of the management command to look up.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when <paramref name="commandType"/> is not a registered management
|
||||
/// command — i.e. not a non-abstract <c>*Command</c> type in the
|
||||
|
||||
@@ -6,5 +6,6 @@ namespace ScadaLink.Commons.Messages.Streaming;
|
||||
/// </summary>
|
||||
public interface ISiteStreamEvent
|
||||
{
|
||||
/// <summary>The unique name of the instance that produced this event.</summary>
|
||||
string InstanceUniqueName { get; }
|
||||
}
|
||||
|
||||
@@ -34,6 +34,11 @@ public enum OpcUaConfigParseStatus
|
||||
/// </summary>
|
||||
public readonly record struct OpcUaConfigParseResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the result with the parsed config and its classification status.
|
||||
/// </summary>
|
||||
/// <param name="config">The parsed endpoint config (or an empty default on malformed input).</param>
|
||||
/// <param name="status">Classification of the parse outcome.</param>
|
||||
public OpcUaConfigParseResult(OpcUaEndpointConfig config, OpcUaConfigParseStatus status)
|
||||
{
|
||||
Config = config;
|
||||
@@ -58,6 +63,8 @@ public readonly record struct OpcUaConfigParseResult
|
||||
/// and <see cref="OpcUaConfigParseStatus.Malformed"/>; callers that need to tell those
|
||||
/// apart should read <see cref="Status"/> directly.
|
||||
/// </summary>
|
||||
/// <param name="config">Receives the parsed endpoint config.</param>
|
||||
/// <param name="isLegacy">Receives true when the source was the legacy flat-dict shape.</param>
|
||||
public void Deconstruct(out OpcUaEndpointConfig config, out bool isLegacy)
|
||||
{
|
||||
config = Config;
|
||||
@@ -83,6 +90,11 @@ public static class OpcUaEndpointConfigSerializer
|
||||
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Serializes an <see cref="OpcUaEndpointConfig"/> to the current typed JSON shape.
|
||||
/// </summary>
|
||||
/// <param name="config">The endpoint configuration to serialize.</param>
|
||||
/// <returns>A JSON string representing the configuration.</returns>
|
||||
public static string Serialize(OpcUaEndpointConfig config)
|
||||
=> JsonSerializer.Serialize(config, JsonOpts);
|
||||
|
||||
@@ -105,6 +117,7 @@ public static class OpcUaEndpointConfigSerializer
|
||||
/// caller should surface an error rather than treating it as the user's saved data.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <param name="json">The stored JSON string to parse; null or blank yields a default typed result.</param>
|
||||
public static OpcUaConfigParseResult Deserialize(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
@@ -161,6 +174,7 @@ public static class OpcUaEndpointConfigSerializer
|
||||
/// IDataConnection.ConnectAsync expects. Keys match the historical convention
|
||||
/// used by OpcUaDataConnection so the adapter can keep that interface.
|
||||
/// </summary>
|
||||
/// <param name="config">The endpoint configuration to flatten.</param>
|
||||
public static IDictionary<string, string> ToFlatDict(OpcUaEndpointConfig config)
|
||||
{
|
||||
var dict = new Dictionary<string, string>
|
||||
@@ -202,6 +216,11 @@ public static class OpcUaEndpointConfigSerializer
|
||||
return dict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reconstructs an <see cref="OpcUaEndpointConfig"/> from the legacy flat string-dict shape.
|
||||
/// </summary>
|
||||
/// <param name="dict">The flat key-value dictionary produced by the legacy shape.</param>
|
||||
/// <returns>The reconstructed endpoint configuration.</returns>
|
||||
public static OpcUaEndpointConfig FromFlatDict(IDictionary<string, string> dict)
|
||||
{
|
||||
var c = new OpcUaEndpointConfig();
|
||||
|
||||
@@ -35,6 +35,9 @@ public static class AuditQueryParamParsers
|
||||
/// <paramref name="rawValues"/> is <c>null</c>, empty, or yields no parseable
|
||||
/// value — so the filter dimension stays unconstrained.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The enum type to parse each raw value into.</typeparam>
|
||||
/// <param name="rawValues">Raw query-parameter string values to parse; may be null.</param>
|
||||
/// <returns>A non-empty list of parsed values, or <c>null</c> if no values could be parsed.</returns>
|
||||
public static IReadOnlyList<TEnum>? ParseEnumList<TEnum>(IEnumerable<string?>? rawValues)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
@@ -59,6 +62,8 @@ public static class AuditQueryParamParsers
|
||||
/// <paramref name="rawValues"/> is <c>null</c>, empty, or every value was
|
||||
/// blank.
|
||||
/// </summary>
|
||||
/// <param name="rawValues">Raw query-parameter string values to trim and filter; may be null.</param>
|
||||
/// <returns>A non-empty list of trimmed strings, or <c>null</c> if no non-blank values remain.</returns>
|
||||
public static IReadOnlyList<string>? ParseStringList(IEnumerable<string?>? rawValues)
|
||||
{
|
||||
if (rawValues is null)
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace ScadaLink.Commons.Types.DataConnections;
|
||||
|
||||
public sealed class OpcUaDeadbandConfig
|
||||
{
|
||||
/// <summary>Gets or sets the OPC UA deadband type (Absolute or Percent).</summary>
|
||||
public OpcUaDeadbandType Type { get; set; } = OpcUaDeadbandType.Absolute;
|
||||
/// <summary>Gets or sets the deadband threshold value; meaning depends on <see cref="Type"/>.</summary>
|
||||
public double Value { get; set; } = 0.0;
|
||||
}
|
||||
|
||||
@@ -1,34 +1,91 @@
|
||||
namespace ScadaLink.Commons.Types.DataConnections;
|
||||
|
||||
/// <summary>
|
||||
/// OPC UA endpoint configuration.
|
||||
/// </summary>
|
||||
public sealed class OpcUaEndpointConfig
|
||||
{
|
||||
// Connection
|
||||
/// <summary>
|
||||
/// The OPC UA endpoint URL.
|
||||
/// </summary>
|
||||
public string EndpointUrl { get; set; } = "";
|
||||
/// <summary>
|
||||
/// The security mode for the connection.
|
||||
/// </summary>
|
||||
public OpcUaSecurityMode SecurityMode { get; set; } = OpcUaSecurityMode.None;
|
||||
/// <summary>
|
||||
/// Whether to automatically accept untrusted certificates.
|
||||
/// </summary>
|
||||
public bool AutoAcceptUntrustedCerts { get; set; } = true;
|
||||
|
||||
// Timing
|
||||
/// <summary>
|
||||
/// Session timeout in milliseconds.
|
||||
/// </summary>
|
||||
public int SessionTimeoutMs { get; set; } = 60000;
|
||||
/// <summary>
|
||||
/// Operation timeout in milliseconds.
|
||||
/// </summary>
|
||||
public int OperationTimeoutMs { get; set; } = 15000;
|
||||
|
||||
// Subscription
|
||||
/// <summary>
|
||||
/// Publishing interval in milliseconds.
|
||||
/// </summary>
|
||||
public int PublishingIntervalMs { get; set; } = 1000;
|
||||
/// <summary>
|
||||
/// Sampling interval in milliseconds.
|
||||
/// </summary>
|
||||
public int SamplingIntervalMs { get; set; } = 1000;
|
||||
/// <summary>
|
||||
/// Queue size for the subscription.
|
||||
/// </summary>
|
||||
public int QueueSize { get; set; } = 10;
|
||||
/// <summary>
|
||||
/// Keep-alive count for the subscription.
|
||||
/// </summary>
|
||||
public int KeepAliveCount { get; set; } = 10;
|
||||
/// <summary>
|
||||
/// Lifetime count for the subscription.
|
||||
/// </summary>
|
||||
public int LifetimeCount { get; set; } = 30;
|
||||
/// <summary>
|
||||
/// Maximum notifications per publish.
|
||||
/// </summary>
|
||||
public int MaxNotificationsPerPublish { get; set; } = 100;
|
||||
/// <summary>
|
||||
/// Whether to discard oldest notifications when queue is full.
|
||||
/// </summary>
|
||||
public bool DiscardOldest { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Subscription priority level.
|
||||
/// </summary>
|
||||
public byte SubscriptionPriority { get; set; } = 0;
|
||||
/// <summary>
|
||||
/// Display name for the subscription.
|
||||
/// </summary>
|
||||
public string SubscriptionDisplayName { get; set; } = "ScadaLink";
|
||||
|
||||
// Read / filter
|
||||
/// <summary>
|
||||
/// Timestamps to return in read operations.
|
||||
/// </summary>
|
||||
public OpcUaTimestampsToReturn TimestampsToReturn { get; set; } = OpcUaTimestampsToReturn.Source;
|
||||
/// <summary>
|
||||
/// Deadband configuration for filtering notifications.
|
||||
/// </summary>
|
||||
public OpcUaDeadbandConfig? Deadband { get; set; }
|
||||
|
||||
// Authentication (optional; null = anonymous)
|
||||
/// <summary>
|
||||
/// User identity configuration for authentication.
|
||||
/// </summary>
|
||||
public OpcUaUserIdentityConfig? UserIdentity { get; set; }
|
||||
|
||||
// Heartbeat (optional)
|
||||
/// <summary>
|
||||
/// Heartbeat configuration for connection monitoring.
|
||||
/// </summary>
|
||||
public OpcUaHeartbeatConfig? Heartbeat { get; set; }
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ namespace ScadaLink.Commons.Types.DataConnections;
|
||||
|
||||
public sealed class OpcUaHeartbeatConfig
|
||||
{
|
||||
/// <summary>OPC UA node path of the heartbeat tag to monitor for activity.</summary>
|
||||
public string TagPath { get; set; } = "";
|
||||
/// <summary>Maximum number of seconds without a value update before the connection is considered unhealthy.</summary>
|
||||
public int MaxSilenceSeconds { get; set; } = 30;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
namespace ScadaLink.Commons.Types.DataConnections;
|
||||
|
||||
/// <summary>
|
||||
/// OPC UA user identity configuration for a data connection endpoint.
|
||||
/// </summary>
|
||||
public sealed class OpcUaUserIdentityConfig
|
||||
{
|
||||
/// <summary>The OPC UA user token type (Anonymous, UserName, or Certificate).</summary>
|
||||
public OpcUaUserTokenType TokenType { get; set; } = OpcUaUserTokenType.Anonymous;
|
||||
/// <summary>Username for UserName token type authentication.</summary>
|
||||
public string Username { get; set; } = "";
|
||||
/// <summary>Password for UserName token type authentication.</summary>
|
||||
public string Password { get; set; } = "";
|
||||
/// <summary>File path to the X.509 certificate for Certificate token type authentication.</summary>
|
||||
public string CertificatePath { get; set; } = "";
|
||||
/// <summary>Password to unlock the certificate private key.</summary>
|
||||
public string CertificatePassword { get; set; } = "";
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ public class DynamicJsonElement : DynamicObject
|
||||
{
|
||||
private readonly JsonElement _element;
|
||||
|
||||
/// <summary>Initializes a new <see cref="DynamicJsonElement"/> wrapping a clone of the given <see cref="JsonElement"/>.</summary>
|
||||
/// <param name="element">The JSON element to wrap; it is cloned to decouple lifetime from the source document.</param>
|
||||
public DynamicJsonElement(JsonElement element)
|
||||
{
|
||||
// Clone detaches the element from its owning JsonDocument so accessing it
|
||||
@@ -27,6 +29,7 @@ public class DynamicJsonElement : DynamicObject
|
||||
_element = element.Clone();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool TryGetMember(GetMemberBinder binder, out object? result)
|
||||
{
|
||||
if (_element.ValueKind == JsonValueKind.Object &&
|
||||
@@ -39,6 +42,7 @@ public class DynamicJsonElement : DynamicObject
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object? result)
|
||||
{
|
||||
// Accept any integral index, not just int. DynamicJsonElement surfaces JSON
|
||||
@@ -80,6 +84,7 @@ public class DynamicJsonElement : DynamicObject
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool TryConvert(ConvertBinder binder, out object? result)
|
||||
{
|
||||
// Conversion to object (or dynamic): never null out a present value. Return the
|
||||
@@ -97,6 +102,7 @@ public class DynamicJsonElement : DynamicObject
|
||||
return result != null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return _element.ValueKind switch
|
||||
|
||||
@@ -6,13 +6,20 @@ namespace ScadaLink.Commons.Types.Flattening;
|
||||
/// </summary>
|
||||
public sealed record ConfigurationDiff
|
||||
{
|
||||
/// <summary>Unique name of the instance this diff applies to.</summary>
|
||||
public string InstanceUniqueName { get; init; } = string.Empty;
|
||||
/// <summary>Revision hash of the previously deployed configuration, or null if not previously deployed.</summary>
|
||||
public string? OldRevisionHash { get; init; }
|
||||
/// <summary>Revision hash of the new configuration being compared.</summary>
|
||||
public string? NewRevisionHash { get; init; }
|
||||
/// <summary>True when any attribute, alarm, or script changes are present.</summary>
|
||||
public bool HasChanges => AttributeChanges.Count > 0 || AlarmChanges.Count > 0 || ScriptChanges.Count > 0;
|
||||
|
||||
/// <summary>Diff entries for resolved attributes.</summary>
|
||||
public IReadOnlyList<DiffEntry<ResolvedAttribute>> AttributeChanges { get; init; } = [];
|
||||
/// <summary>Diff entries for resolved alarms.</summary>
|
||||
public IReadOnlyList<DiffEntry<ResolvedAlarm>> AlarmChanges { get; init; } = [];
|
||||
/// <summary>Diff entries for resolved scripts.</summary>
|
||||
public IReadOnlyList<DiffEntry<ResolvedScript>> ScriptChanges { get; init; } = [];
|
||||
}
|
||||
|
||||
@@ -21,7 +28,9 @@ public sealed record ConfigurationDiff
|
||||
/// </summary>
|
||||
public sealed record DiffEntry<T>
|
||||
{
|
||||
/// <summary>The canonical name of the changed entity.</summary>
|
||||
public string CanonicalName { get; init; } = string.Empty;
|
||||
/// <summary>The type of change: Added, Removed, or Changed.</summary>
|
||||
public DiffChangeType ChangeType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -9,13 +9,21 @@ namespace ScadaLink.Commons.Types.Flattening;
|
||||
/// </summary>
|
||||
public sealed record FlattenedConfiguration
|
||||
{
|
||||
/// <summary>Gets the instance unique name.</summary>
|
||||
public string InstanceUniqueName { get; init; } = string.Empty;
|
||||
/// <summary>Gets the template ID.</summary>
|
||||
public int TemplateId { get; init; }
|
||||
/// <summary>Gets the site ID.</summary>
|
||||
public int SiteId { get; init; }
|
||||
/// <summary>Gets the area ID, if any.</summary>
|
||||
public int? AreaId { get; init; }
|
||||
/// <summary>Gets the resolved attributes.</summary>
|
||||
public IReadOnlyList<ResolvedAttribute> Attributes { get; init; } = [];
|
||||
/// <summary>Gets the resolved alarms.</summary>
|
||||
public IReadOnlyList<ResolvedAlarm> Alarms { get; init; } = [];
|
||||
/// <summary>Gets the resolved scripts.</summary>
|
||||
public IReadOnlyList<ResolvedScript> Scripts { get; init; } = [];
|
||||
/// <summary>Gets the UTC timestamp when this configuration was generated.</summary>
|
||||
public DateTimeOffset GeneratedAtUtc { get; init; } = DateTimeOffset.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
@@ -31,9 +39,13 @@ public sealed record FlattenedConfiguration
|
||||
/// </summary>
|
||||
public sealed record ConnectionConfig
|
||||
{
|
||||
/// <summary>Gets the protocol name (e.g., "OpcUa").</summary>
|
||||
public string Protocol { get; init; } = string.Empty;
|
||||
/// <summary>Gets the primary configuration as JSON.</summary>
|
||||
public string? ConfigurationJson { get; init; }
|
||||
/// <summary>Gets the backup configuration as JSON.</summary>
|
||||
public string? BackupConfigurationJson { get; init; }
|
||||
/// <summary>Gets the number of failover retries.</summary>
|
||||
public int FailoverRetryCount { get; init; } = 3;
|
||||
}
|
||||
|
||||
@@ -48,9 +60,13 @@ public sealed record ResolvedAttribute
|
||||
/// </summary>
|
||||
public string CanonicalName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>Gets the attribute value.</summary>
|
||||
public string? Value { get; init; }
|
||||
/// <summary>Gets the data type name.</summary>
|
||||
public string DataType { get; init; } = string.Empty;
|
||||
/// <summary>Gets whether the attribute is locked.</summary>
|
||||
public bool IsLocked { get; init; }
|
||||
/// <summary>Gets the attribute description.</summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
@@ -84,10 +100,15 @@ public sealed record ResolvedAttribute
|
||||
/// </summary>
|
||||
public sealed record ResolvedAlarm
|
||||
{
|
||||
/// <summary>Gets the path-qualified canonical name.</summary>
|
||||
public string CanonicalName { get; init; } = string.Empty;
|
||||
/// <summary>Gets the alarm description.</summary>
|
||||
public string? Description { get; init; }
|
||||
/// <summary>Gets the priority level.</summary>
|
||||
public int PriorityLevel { get; init; }
|
||||
/// <summary>Gets whether the alarm is locked.</summary>
|
||||
public bool IsLocked { get; init; }
|
||||
/// <summary>Gets the trigger type.</summary>
|
||||
public string TriggerType { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
@@ -100,6 +121,7 @@ public sealed record ResolvedAlarm
|
||||
/// </summary>
|
||||
public string? OnTriggerScriptCanonicalName { get; init; }
|
||||
|
||||
/// <summary>Gets the source of this alarm value: "Template", "Inherited", "Composed", or "Override".</summary>
|
||||
public string Source { get; init; } = "Template";
|
||||
}
|
||||
|
||||
@@ -108,10 +130,15 @@ public sealed record ResolvedAlarm
|
||||
/// </summary>
|
||||
public sealed record ResolvedScript
|
||||
{
|
||||
/// <summary>Gets the path-qualified canonical name.</summary>
|
||||
public string CanonicalName { get; init; } = string.Empty;
|
||||
/// <summary>Gets the script code.</summary>
|
||||
public string Code { get; init; } = string.Empty;
|
||||
/// <summary>Gets whether the script is locked.</summary>
|
||||
public bool IsLocked { get; init; }
|
||||
/// <summary>Gets the trigger type.</summary>
|
||||
public string? TriggerType { get; init; }
|
||||
/// <summary>Gets the trigger configuration.</summary>
|
||||
public string? TriggerConfiguration { get; init; }
|
||||
|
||||
/// <summary>
|
||||
@@ -124,7 +151,9 @@ public sealed record ResolvedScript
|
||||
/// </summary>
|
||||
public string? ReturnDefinition { get; init; }
|
||||
|
||||
/// <summary>Gets the minimum time between script executions.</summary>
|
||||
public TimeSpan? MinTimeBetweenRuns { get; init; }
|
||||
/// <summary>Gets the source of this script.</summary>
|
||||
public string Source { get; init; } = "Template";
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,15 +5,23 @@ namespace ScadaLink.Commons.Types.Flattening;
|
||||
/// </summary>
|
||||
public sealed record ValidationResult
|
||||
{
|
||||
/// <summary>True when there are no validation errors.</summary>
|
||||
public bool IsValid => Errors.Count == 0;
|
||||
/// <summary>Validation errors that block the operation.</summary>
|
||||
public IReadOnlyList<ValidationEntry> Errors { get; init; } = [];
|
||||
/// <summary>Non-blocking validation warnings.</summary>
|
||||
public IReadOnlyList<ValidationEntry> Warnings { get; init; } = [];
|
||||
|
||||
/// <summary>Returns a result with no errors or warnings.</summary>
|
||||
public static ValidationResult Success() => new();
|
||||
|
||||
/// <summary>Returns a result containing the given errors.</summary>
|
||||
/// <param name="errors">The validation errors to include.</param>
|
||||
public static ValidationResult FromErrors(params ValidationEntry[] errors) =>
|
||||
new() { Errors = errors };
|
||||
|
||||
/// <summary>Merges multiple validation results into a single combined result.</summary>
|
||||
/// <param name="results">The results to merge.</param>
|
||||
public static ValidationResult Merge(params ValidationResult[] results)
|
||||
{
|
||||
var errors = new List<ValidationEntry>();
|
||||
@@ -32,7 +40,9 @@ public sealed record ValidationResult
|
||||
/// </summary>
|
||||
public sealed record ValidationEntry
|
||||
{
|
||||
/// <summary>The category classifying the kind of validation failure.</summary>
|
||||
public ValidationCategory Category { get; init; }
|
||||
/// <summary>Human-readable description of the validation issue.</summary>
|
||||
public string Message { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
@@ -40,9 +50,17 @@ public sealed record ValidationEntry
|
||||
/// </summary>
|
||||
public string? EntityName { get; init; }
|
||||
|
||||
/// <summary>Creates an error entry with the given category, message, and optional entity name.</summary>
|
||||
/// <param name="category">The validation category.</param>
|
||||
/// <param name="message">The error message.</param>
|
||||
/// <param name="entityName">The canonical name of the entity that caused the error, if any.</param>
|
||||
public static ValidationEntry Error(ValidationCategory category, string message, string? entityName = null) =>
|
||||
new() { Category = category, Message = message, EntityName = entityName };
|
||||
|
||||
/// <summary>Creates a warning entry with the given category, message, and optional entity name.</summary>
|
||||
/// <param name="category">The validation category.</param>
|
||||
/// <param name="message">The warning message.</param>
|
||||
/// <param name="entityName">The canonical name of the entity that triggered the warning, if any.</param>
|
||||
public static ValidationEntry Warning(ValidationCategory category, string message, string? entityName = null) =>
|
||||
new() { Category = category, Message = message, EntityName = entityName };
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ public interface IApiKeyHasher
|
||||
/// The same input always produces the same output (deterministic), which keeps
|
||||
/// the by-value lookup working.
|
||||
/// </summary>
|
||||
/// <param name="apiKey">The raw API key to hash.</param>
|
||||
/// <returns>A Base64-encoded HMAC-SHA256 hash of the key.</returns>
|
||||
string Hash(string apiKey);
|
||||
}
|
||||
|
||||
@@ -54,6 +56,7 @@ public sealed class ApiKeyHasher : IApiKeyHasher
|
||||
/// <summary>
|
||||
/// Creates a hasher keyed with the given server-side pepper.
|
||||
/// </summary>
|
||||
/// <param name="pepper">Server-side HMAC key; must be at least <see cref="MinimumPepperLength"/> characters.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown if <paramref name="pepper"/> is null, blank, or shorter than
|
||||
/// <see cref="MinimumPepperLength"/> — a missing or weak pepper is a deployment
|
||||
|
||||
@@ -9,7 +9,10 @@ namespace ScadaLink.Commons.Types.InboundApi;
|
||||
/// </summary>
|
||||
public class ParameterDefinition
|
||||
{
|
||||
/// <summary>Gets or sets the parameter name as it must appear in the JSON request body.</summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
/// <summary>Gets or sets the expected type (e.g. "String", "Integer", "Float", "Boolean", "Object", "List").</summary>
|
||||
public string Type { get; set; } = "String";
|
||||
/// <summary>Gets or sets whether this parameter must be present in the request body.</summary>
|
||||
public bool Required { get; set; } = true;
|
||||
}
|
||||
|
||||
@@ -22,27 +22,40 @@ public sealed class Result<T>
|
||||
IsSuccess = false;
|
||||
}
|
||||
|
||||
/// <summary>True when the result represents a successful outcome.</summary>
|
||||
public bool IsSuccess { get; }
|
||||
|
||||
/// <summary>True when the result represents a failure outcome.</summary>
|
||||
public bool IsFailure => !IsSuccess;
|
||||
|
||||
/// <summary>The success value; throws <see cref="InvalidOperationException"/> when <see cref="IsFailure"/> is true.</summary>
|
||||
public T Value => IsSuccess
|
||||
? _value!
|
||||
: throw new InvalidOperationException("Cannot access Value on a failed Result. Error: " + _error);
|
||||
|
||||
/// <summary>The error message; throws <see cref="InvalidOperationException"/> when <see cref="IsSuccess"/> is true.</summary>
|
||||
public string Error => IsFailure
|
||||
? _error!
|
||||
: throw new InvalidOperationException("Cannot access Error on a successful Result.");
|
||||
|
||||
/// <summary>Creates a successful result carrying the given value.</summary>
|
||||
/// <param name="value">The success value.</param>
|
||||
/// <returns>A successful <see cref="Result{T}"/>.</returns>
|
||||
public static Result<T> Success(T value) => new(value);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a failed result carrying the given error message.
|
||||
/// </summary>
|
||||
/// <param name="error">Non-blank error message describing the failure.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="error"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="error"/> is empty or whitespace.</exception>
|
||||
public static Result<T> Failure(string error) => new(error);
|
||||
|
||||
/// <summary>Pattern-matches the result, invoking either the success or failure delegate.</summary>
|
||||
/// <typeparam name="TResult">The return type of both delegates.</typeparam>
|
||||
/// <param name="onSuccess">Delegate invoked with the value when the result is successful.</param>
|
||||
/// <param name="onFailure">Delegate invoked with the error message when the result is a failure.</param>
|
||||
/// <returns>The value returned by the invoked delegate.</returns>
|
||||
public TResult Match<TResult>(Func<T, TResult> onSuccess, Func<string, TResult> onFailure) =>
|
||||
IsSuccess ? onSuccess(_value!) : onFailure(_error!);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ namespace ScadaLink.Commons.Types;
|
||||
/// </summary>
|
||||
public static class ScriptArgs
|
||||
{
|
||||
/// <summary>Normalizes a loosely-typed parameters argument into a read-only string-keyed dictionary, or null if no parameters were supplied.</summary>
|
||||
/// <param name="parameters">Null, an existing dictionary, or an anonymous object whose properties become parameter entries.</param>
|
||||
/// <returns>A normalized read-only dictionary, or null when <paramref name="parameters"/> is null.</returns>
|
||||
public static IReadOnlyDictionary<string, object?>? Normalize(object? parameters)
|
||||
{
|
||||
switch (parameters)
|
||||
|
||||
@@ -13,11 +13,18 @@ public class ScriptParameters : IReadOnlyDictionary<string, object?>
|
||||
{
|
||||
private readonly IReadOnlyDictionary<string, object?> _inner;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScriptParameters"/> class with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="parameters">The underlying parameter dictionary.</param>
|
||||
public ScriptParameters(IReadOnlyDictionary<string, object?> parameters)
|
||||
{
|
||||
_inner = parameters ?? throw new ArgumentNullException(nameof(parameters));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScriptParameters"/> class with an empty parameter dictionary.
|
||||
/// </summary>
|
||||
public ScriptParameters() : this(new Dictionary<string, object?>()) { }
|
||||
|
||||
/// <summary>
|
||||
@@ -30,6 +37,9 @@ public class ScriptParameters : IReadOnlyDictionary<string, object?>
|
||||
/// <item><c>Get<List<int>>("key")</c> — converts list to typed List; throws on first bad element.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The target type for the parameter value.</typeparam>
|
||||
/// <param name="key">The parameter key.</param>
|
||||
/// <returns>The converted parameter value.</returns>
|
||||
public T Get<T>(string key)
|
||||
{
|
||||
var targetType = typeof(T);
|
||||
@@ -220,13 +230,43 @@ public class ScriptParameters : IReadOnlyDictionary<string, object?>
|
||||
}
|
||||
|
||||
// IReadOnlyDictionary<string, object?> implementation
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The parameter key.</param>
|
||||
/// <returns>The parameter value, or null if the key is not found.</returns>
|
||||
public object? this[string key] => _inner[key];
|
||||
/// <summary>
|
||||
/// Gets the collection of parameter keys.
|
||||
/// </summary>
|
||||
public IEnumerable<string> Keys => _inner.Keys;
|
||||
/// <summary>
|
||||
/// Gets the collection of parameter values.
|
||||
/// </summary>
|
||||
public IEnumerable<object?> Values => _inner.Values;
|
||||
/// <summary>
|
||||
/// Gets the number of parameters.
|
||||
/// </summary>
|
||||
public int Count => _inner.Count;
|
||||
/// <summary>
|
||||
/// Determines whether the parameters contain the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to locate.</param>
|
||||
/// <returns>True if the key is found; false otherwise.</returns>
|
||||
public bool ContainsKey(string key) => _inner.ContainsKey(key);
|
||||
/// <summary>
|
||||
/// Attempts to get the value associated with the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to locate.</param>
|
||||
/// <param name="value">The value associated with the key if found; otherwise null.</param>
|
||||
/// <returns>True if the key is found; false otherwise.</returns>
|
||||
public bool TryGetValue(string key, out object? value) => _inner.TryGetValue(key, out value);
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the parameters.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator for the parameters.</returns>
|
||||
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator() => _inner.GetEnumerator();
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
@@ -236,7 +276,16 @@ public class ScriptParameters : IReadOnlyDictionary<string, object?>
|
||||
/// </summary>
|
||||
public class ScriptParameterException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScriptParameterException"/> class with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
public ScriptParameterException(string message) : base(message) { }
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScriptParameterException"/> class with a specified error message and inner exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception.</param>
|
||||
public ScriptParameterException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
}
|
||||
|
||||
@@ -11,8 +11,11 @@ namespace ScadaLink.Commons.Types.Scripts;
|
||||
/// </summary>
|
||||
public sealed class AlarmContext
|
||||
{
|
||||
/// <summary>Name of the alarm that fired.</summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
/// <summary>Severity level of the alarm; <see cref="AlarmLevel.None"/> for binary trigger types.</summary>
|
||||
public AlarmLevel Level { get; init; } = AlarmLevel.None;
|
||||
/// <summary>Operator-assigned priority of the alarm.</summary>
|
||||
public int Priority { get; init; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,5 +13,6 @@ public sealed record ScriptScope(string SelfPath, string? ParentPath)
|
||||
/// <summary>Scope for a script directly on the root template (no compositions).</summary>
|
||||
public static readonly ScriptScope Root = new("", null);
|
||||
|
||||
/// <summary>Gets a value indicating whether this script has a parent composition path.</summary>
|
||||
public bool HasParent => ParentPath != null;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ public sealed class StaleTagMonitor : IDisposable
|
||||
/// </summary>
|
||||
private long _generation;
|
||||
|
||||
/// <summary>Initializes a new <see cref="StaleTagMonitor"/> that fires <see cref="Stale"/> if no value is received within <paramref name="maxSilence"/>.</summary>
|
||||
/// <param name="maxSilence">The maximum time with no received value before the <see cref="Stale"/> event fires; must be positive.</param>
|
||||
public StaleTagMonitor(TimeSpan maxSilence)
|
||||
{
|
||||
if (maxSilence <= TimeSpan.Zero)
|
||||
@@ -46,6 +48,7 @@ public sealed class StaleTagMonitor : IDisposable
|
||||
/// </summary>
|
||||
public event Action? Stale;
|
||||
|
||||
/// <summary>Gets the maximum silence interval after which the <see cref="Stale"/> event fires.</summary>
|
||||
public TimeSpan MaxSilence => _maxSilence;
|
||||
|
||||
/// <summary>
|
||||
@@ -105,6 +108,7 @@ public sealed class StaleTagMonitor : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Stops monitoring and disposes the internal timer.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
|
||||
@@ -29,6 +29,7 @@ public readonly record struct TrackedOperationId(Guid Value)
|
||||
/// is not a valid GUID — callers crossing untrusted boundaries should use
|
||||
/// <see cref="TryParse"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="s">GUID string to parse.</param>
|
||||
public static TrackedOperationId Parse(string s) => new(Guid.Parse(s));
|
||||
|
||||
/// <summary>
|
||||
@@ -36,6 +37,8 @@ public readonly record struct TrackedOperationId(Guid Value)
|
||||
/// or non-GUID input; <paramref name="result"/> is <c>default</c> on
|
||||
/// failure.
|
||||
/// </summary>
|
||||
/// <param name="s">GUID string to parse, or null.</param>
|
||||
/// <param name="result">Parsed value on success; default on failure.</param>
|
||||
public static bool TryParse(string? s, out TrackedOperationId result)
|
||||
{
|
||||
if (Guid.TryParse(s, out var g))
|
||||
|
||||
@@ -2,10 +2,16 @@ namespace ScadaLink.Commons.Types.Transport;
|
||||
|
||||
public sealed class BundleSession
|
||||
{
|
||||
/// <summary>Unique identifier for this import session.</summary>
|
||||
public Guid SessionId { get; init; }
|
||||
/// <summary>Parsed manifest from the uploaded bundle.</summary>
|
||||
public BundleManifest Manifest { get; init; } = null!;
|
||||
/// <summary>Decrypted bundle content bytes; empty until the bundle is successfully unlocked.</summary>
|
||||
public byte[] DecryptedContent { get; init; } = Array.Empty<byte>();
|
||||
/// <summary>UTC timestamp after which this session is considered expired and must be re-uploaded.</summary>
|
||||
public DateTimeOffset ExpiresAt { get; init; }
|
||||
/// <summary>Number of failed passphrase unlock attempts for this session.</summary>
|
||||
public int FailedUnlockAttempts { get; set; }
|
||||
/// <summary>True when three or more unlock attempts have failed, locking further attempts.</summary>
|
||||
public bool Locked => FailedUnlockAttempts >= 3;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ public static class ValueFormatter
|
||||
/// Formats a value as a string. Returns the value's string representation for
|
||||
/// scalars and comma-separated elements for array/collection types.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to format; null returns an empty string.</param>
|
||||
/// <remarks>
|
||||
/// Formatting is <see cref="CultureInfo.InvariantCulture">culture-invariant</see>:
|
||||
/// numbers and <see cref="DateTime"/> values render the same regardless of the
|
||||
|
||||
@@ -11,6 +11,11 @@ namespace ScadaLink.Commons.Validators;
|
||||
/// </summary>
|
||||
public static class OpcUaEndpointConfigValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates all fields of an <see cref="OpcUaEndpointConfig"/>, returning errors with optionally-prefixed field names.
|
||||
/// </summary>
|
||||
/// <param name="config">The OPC UA endpoint configuration to validate.</param>
|
||||
/// <param name="fieldPrefix">Optional prefix prepended to each field name in error entries (e.g., "Primary.").</param>
|
||||
public static ValidationResult Validate(OpcUaEndpointConfig config, string fieldPrefix = "")
|
||||
{
|
||||
var errors = new List<ValidationEntry>();
|
||||
|
||||
Reference in New Issue
Block a user